<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<meta name="theme-color" content="#222"><meta name="generator" content="Hexo 6.3.0">

  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">



<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css" integrity="sha256-Z1K5uhUaJXA7Ll0XrZ/0JhX4lAtZFpT6jkKrEDT0drU=" crossorigin="anonymous">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.1.1/animate.min.css" integrity="sha256-PR7ttpcvz8qrF57fur/yAx1qXMFJeJFiA6pSzWi0OIE=" crossorigin="anonymous">

<script class="next-config" data-name="main" type="application/json">{"hostname":"example.com","root":"/","images":"/images","scheme":"Muse","darkmode":false,"version":"8.14.1","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12},"copycode":{"enable":false,"style":null},"bookmark":{"enable":false,"color":"#222","save":"auto"},"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"stickytabs":false,"motion":{"enable":true,"async":false,"transition":{"menu_item":"fadeInDown","post_block":"fadeIn","post_header":"fadeInDown","post_body":"fadeInDown","coll_header":"fadeInLeft","sidebar":"fadeInUp"}},"prism":false,"i18n":{"placeholder":"搜索...","empty":"没有找到任何搜索结果：${query}","hits_time":"找到 ${hits} 个搜索结果（用时 ${time} 毫秒）","hits":"找到 ${hits} 个搜索结果"},"path":"/search.xml","localsearch":{"enable":true,"trigger":"auto","top_n_per_article":-1,"unescape":false,"preload":false}}</script><script src="/js/config.js"></script>

    <meta property="og:type" content="website">
<meta property="og:title" content="JsyBlog">
<meta property="og:url" content="http://example.com/page/19/index.html">
<meta property="og:site_name" content="JsyBlog">
<meta property="og:locale" content="zh_CN">
<meta property="article:author" content="SongyangJi">
<meta name="twitter:card" content="summary">


<link rel="canonical" href="http://example.com/page/19/">



<script class="next-config" data-name="page" type="application/json">{"sidebar":"","isHome":true,"isPost":false,"lang":"zh-CN","comments":"","permalink":"","path":"page/19/index.html","title":""}</script>

<script class="next-config" data-name="calendar" type="application/json">""</script>
<title>JsyBlog</title>
  








  <noscript>
    <link rel="stylesheet" href="/css/noscript.css">
  </noscript>
</head>

<body itemscope itemtype="http://schema.org/WebPage" class="use-motion">
  <div class="headband"></div>

  <main class="main">
    <div class="column">
      <header class="header" itemscope itemtype="http://schema.org/WPHeader"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏" role="button">
        <span class="toggle-line"></span>
        <span class="toggle-line"></span>
        <span class="toggle-line"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <i class="logo-line"></i>
      <h1 class="site-title">JsyBlog</h1>
      <i class="logo-line"></i>
    </a>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger" aria-label="搜索" role="button">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>



<nav class="site-nav">
  <ul class="main-menu menu"><li class="menu-item menu-item-home"><a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a></li><li class="menu-item menu-item-tags"><a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a></li><li class="menu-item menu-item-categories"><a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a></li><li class="menu-item menu-item-archives"><a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a></li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup"><div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off" maxlength="80"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close" role="button">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div class="search-result-container no-result">
  <div class="search-result-icon">
    <i class="fa fa-spinner fa-pulse fa-5x"></i>
  </div>
</div>

    </div>
  </div>

</header>
        
  
  <aside class="sidebar">

    <div class="sidebar-inner sidebar-overview-active">
      <ul class="sidebar-nav">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <div class="sidebar-panel-container">
        <!--noindex-->
        <div class="post-toc-wrap sidebar-panel">
        </div>
        <!--/noindex-->

        <div class="site-overview-wrap sidebar-panel">
          <div class="site-author animated" itemprop="author" itemscope itemtype="http://schema.org/Person">
  <p class="site-author-name" itemprop="name">SongyangJi</p>
  <div class="site-description" itemprop="description"></div>
</div>
<div class="site-state-wrap animated">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
        <a href="/archives/">
          <span class="site-state-item-count">251</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
          <a href="/categories/">
        <span class="site-state-item-count">45</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
          <a href="/tags/">
        <span class="site-state-item-count">109</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>

        </div>
      </div>
    </div>

    
  </aside>


    </div>

    <div class="main-inner index posts-expand">

    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/11/20/Linux%E5%A4%9A%E8%BF%9B%E7%A8%8B%E3%80%81%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%BC%96%E7%A8%8B/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="SongyangJi">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="JsyBlog">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | JsyBlog">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2021/11/20/Linux%E5%A4%9A%E8%BF%9B%E7%A8%8B%E3%80%81%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%BC%96%E7%A8%8B/" class="post-title-link" itemprop="url">Linux多进程、多线程编程</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2021-11-20 22:13:12" itemprop="dateCreated datePublished" datetime="2021-11-20T22:13:12+08:00">2021-11-20</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">更新于</span>
      <time title="修改时间：2022-03-28 15:28:06" itemprop="dateModified" datetime="2022-03-28T15:28:06+08:00">2022-03-28</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/Linux/" itemprop="url" rel="index"><span itemprop="name">Linux</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h1 id="进程-x2F-线程相关"><a href="#进程-x2F-线程相关" class="headerlink" title="进程&#x2F;线程相关"></a>进程&#x2F;线程相关</h1><h2 id="线程相关"><a href="#线程相关" class="headerlink" title="线程相关"></a>线程相关</h2><h2 id="background-information"><a href="#background-information" class="headerlink" title="background information"></a>background information</h2><p>线程和进程的理论概念不再赘述。<br>Linux 中，系统是不认识线程还是进程的，它只认识 task。</p>
<blockquote>
<p>下面的阐述都是 Unix like 下的有关线程的语义。</p>
</blockquote>
<p>主线程和子线程</p>
<ul>
<li>共享： 用户区内，除了栈区是不共享的，其余都是共享的。</li>
<li>不共享： 栈区（当有 1 主 + 4 子线程时候，栈区会被平分为 5 份）</li>
</ul>
<p>多进程共享的资源（fork、clone出的子进程和父进程）：</p>
<ul>
<li>代码</li>
<li>文件描述符</li>
<li>内存映射区 –mmap</li>
</ul>
<p>多线程共享的资源：</p>
<ul>
<li><p>堆</p>
</li>
<li><p>全局变量 </p>
</li>
<li><p>线程号和线程 ID 是有区别的<br>查看方式： 找到程序的进程 ID后， <code>ps -Lf $(pid)</code>，LWP那一列即为线程ID。</p>
</li>
</ul>
<h3 id="pthread-create"><a href="#pthread-create" class="headerlink" title="pthread_create"></a>pthread_create</h3><ul>
<li><p>头文件:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;pthread.h&gt;</span></span></span><br></pre></td></tr></table></figure>
<p>pthread非linux系统的默认库， 需手动链接-线程库 -lpthread</p>
</li>
<li><p>函数说明:<br>返回成功时，由 tidp 指向的内存单元被设置为新创建线程的线程ID。a<br>ttr参数用于指定各种不同的线程属性。<br>新创建的线程从start_rtn函数的地址开始运行，该函数只有一个万能指针参数arg。<br>如果需要向start_rtn函数传递的参数不止一个，那么需要把这些参数放到一个结构体中，然后把这个结构的地址作为arg的参数传入。</p>
</li>
<li><p>函数定义</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">pthread_create</span><span class="params">(<span class="type">pthread_t</span> *tidp,<span class="type">const</span> <span class="type">pthread_attr_t</span> *attr, <span class="type">void</span> *(*start_rtn)(<span class="type">void</span>*),<span class="type">void</span> *arg)</span>;</span><br></pre></td></tr></table></figure>


</li>
<li><p>参数说明：<br>第一个参数为指向线程标识符的指针。<br>第二个参数用来设置线程属性。<br>第三个参数是线程运行函数的起始地址。<br>最后一个参数是运行函数的参数。</p>
</li>
<li><p>返回值<br>若线程创建成功，则返回0。若线程创建失败，则返回出错编号，并且*thread中的内容是未定义的。</p>
</li>
<li><p>代码</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> num = <span class="number">13</span>; <span class="comment">//设置为全局变量，在全局区域，共享</span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span>* <span class="title function_">myfun</span><span class="params">(<span class="type">void</span>* arg)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">void</span>* p = (<span class="type">void</span> *)&amp;num;  <span class="comment">//传一个地址进去（voi* 也是 4 个字节）</span></span><br><span class="line">    <span class="type">pthread_t</span> id[<span class="number">5</span>] = &#123;<span class="number">0</span>&#125;;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; <span class="number">5</span>; i++) &#123;</span><br><span class="line">        pthread_create(&amp;(id[i]), <span class="literal">NULL</span>, myfun, p);</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;i = %d, thread id: %ld\n&quot;</span>, i, id[i]);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span>* <span class="title function_">myfun</span><span class="params">(<span class="type">void</span>* arg)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;num = %d, child thread id: %ld\n&quot;</span>, (*((<span class="type">int</span> *)arg))++, pthread_self());</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>参考链接<br><a target="_blank" rel="noopener" href="https://man7.org/linux/man-pages/man3/pthread_create.3.html">pthread_create(3) — Linux manual page</a><br><a target="_blank" rel="noopener" href="https://xmuli.tech/posts/28f8209f/">Linux中创建多线程实例pthread_create()</a></p>
</li>
</ul>
<h3 id="pthread-join"><a href="#pthread-join" class="headerlink" title="pthread_join"></a>pthread_join</h3><p>pthread_join()即是子线程合入主线程，主线程阻塞等待子线程结束，然后回收子线程资源。</p>
<p>注意，默认情况下，资源是不会随着子线程的exit或return而回收的。</p>
<ul>
<li><p>头文件:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br></pre></td></tr></table></figure>
</li>
<li><p>函数说明:<br>thread_join()函数，以阻塞的方式等待thread指定的线程结束。当函数返回时，被等待线程的资源被收回。<br>如果线程已经结束，那么该函数会立即返回。并且thread指定的线程必须是 joinable 的。</p>
</li>
<li><p>函数定义</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">pthread_join</span><span class="params">(<span class="type">pthread_t</span> thread, <span class="type">void</span> **retval)</span>;</span><br></pre></td></tr></table></figure>
<p>thread: 线程标识符，即线程ID，标识唯一线程。<br>retval: 用户定义的指针，用来存储被等待线程的返回值。</p>
</li>
<li><p>返回值<br>0代表成功。 失败，返回的则是错误号。</p>
</li>
<li><p>示例代码：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span> *<span class="title function_">thread_function</span><span class="params">(<span class="type">void</span> *arg)</span> &#123;</span><br><span class="line">    <span class="type">int</span> i;</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; <span class="number">8</span>; i++) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Thread working... %d \n&quot;</span>, i);</span><br><span class="line">        sleep(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">void</span>)</span> &#123;</span><br><span class="line">    <span class="type">pthread_t</span> mythread;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (pthread_create(&amp;mythread, <span class="literal">NULL</span>, thread_function, <span class="literal">NULL</span>)) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;error creating thread.&quot;</span>);</span><br><span class="line">        <span class="built_in">abort</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (pthread_join(mythread, <span class="literal">NULL</span>)) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;error join thread.&quot;</span>);</span><br><span class="line">        <span class="built_in">abort</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;thread done! \n&quot;</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> 输出</span></span><br><span class="line"><span class="comment">Thread working...! 0 </span></span><br><span class="line"><span class="comment">Thread working...! 1 </span></span><br><span class="line"><span class="comment">Thread working...! 2 </span></span><br><span class="line"><span class="comment">Thread working...! 3 </span></span><br><span class="line"><span class="comment">Thread working...! 4 </span></span><br><span class="line"><span class="comment">Thread working...! 5 </span></span><br><span class="line"><span class="comment">Thread working...! 6 </span></span><br><span class="line"><span class="comment">Thread working...! 7 </span></span><br><span class="line"><span class="comment">thread done! </span></span><br><span class="line"><span class="comment"> */</span></span><br></pre></td></tr></table></figure></li>
</ul>
<p>如果去掉<code>pthread_join</code>的调用的话，<br>输出为：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">thread done! </span><br><span class="line">Thread working... 0 </span><br><span class="line">Process finished with exit code 0</span><br></pre></td></tr></table></figure>
<p>也就是说，子线程来不及执行它的函数，就因为父线程的死亡而被迫终结了。</p>
<h3 id="pthread-detach"><a href="#pthread-detach" class="headerlink" title="pthread_detach"></a>pthread_detach</h3><ul>
<li><p>头文件:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br></pre></td></tr></table></figure>
</li>
<li><p>函数说明:<br>pthread_join()函数的替代函数，可回收创建时detachstate属性设置为PTHREAD_CREATE_JOINABLE的线程的存储空间。<br>该函数不会阻塞父线程。<br>pthread_join()函数用于只是应用程序在线程tid终止时回收其存储空间。</p>
</li>
<li><p>函数定义</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">pthread_detach</span><span class="params">(<span class="type">pthread_t</span> tid)</span>;</span><br></pre></td></tr></table></figure>
</li>
<li><p>返回值<br>thread_detach() 在调用成功完成之后返回零。其他任何返回值都表示出现了错误。</p>
</li>
</ul>
<p>注意，即使如此，父线程退出时，子线程仍然会强制退出。</p>
<h3 id="pthread-cancel"><a href="#pthread-cancel" class="headerlink" title="pthread_cancel"></a>pthread_cancel</h3><ul>
<li><p>函数说明：<br>发送终止信号给thread线程，如果成功则返回0，否则为非0值。发送成功并不意味着thread会终止。</p>
</li>
<li><p>函数定义：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">pthread_cancel</span><span class="params">(<span class="type">pthread_t</span> thread)</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>示例代码：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @Author: 吉松阳</span></span><br><span class="line"><span class="comment"> * @Date: 2021/9/26</span></span><br><span class="line"><span class="comment"> * @Description: </span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">print_message_function</span><span class="params">(<span class="type">void</span> *ptr)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">pthread_t</span> thread1;</span><br><span class="line">    pthread_create(&amp;thread1, <span class="literal">NULL</span>, (<span class="type">void</span> *) &amp;print_message_function, (<span class="type">void</span> *) <span class="number">0</span>);</span><br><span class="line">    sleep(<span class="number">3</span>);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;main thread\n&quot;</span>);</span><br><span class="line">    pthread_cancel(thread1);</span><br><span class="line">    sleep(<span class="number">7</span>);</span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">print_message_function</span><span class="params">(<span class="type">void</span> *ptr)</span> &#123;</span><br><span class="line">    pthread_detach(pthread_self());</span><br><span class="line">    sleep(<span class="number">6</span>);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;child thread\n&quot;</span>);</span><br><span class="line">    pthread_exit(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 实验证明 pthread_exit 确实起作用了</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>参考链接：<br><a target="_blank" rel="noopener" href="https://www.cnblogs.com/lijunamneg/archive/2013/01/25/2877211.html">线程取消(pthread_cancel)</a></p>
</li>
</ul>
<h2 id="信号处理相关"><a href="#信号处理相关" class="headerlink" title="信号处理相关"></a>信号处理相关</h2><h3 id="raise"><a href="#raise" class="headerlink" title="raise"></a>raise</h3><ul>
<li><p>头文件:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;signal.h&gt;</span></span></span><br></pre></td></tr></table></figure>
</li>
<li><p>函数说明:<br>C 库函数, 会促使生成信号 sig。sig 参数与 SIG 宏兼容。</p>
</li>
<li><p>函数定义</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// sig -- 要发送的信号码。</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">raise</span><span class="params">(<span class="type">int</span> sig)</span></span><br></pre></td></tr></table></figure></li>
</ul>
<p>查看所有信号：使用 <code>kill -l</code></p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">songyangji@SongyangJi-Ubuntu-DeskStop:~$ kill -l</span><br><span class="line"> 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP</span><br><span class="line"> 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1</span><br><span class="line">11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM</span><br><span class="line">16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP</span><br><span class="line">21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ</span><br><span class="line">26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR</span><br><span class="line">31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3</span><br><span class="line">38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8</span><br><span class="line">43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13</span><br><span class="line">48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12</span><br><span class="line">53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7</span><br><span class="line">58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2</span><br></pre></td></tr></table></figure>

<p>值得注意的是，<br>当一个进程调用fork时，因为子进程在开始时复制父进程的存储映像，信号捕捉函数的地址在子进程中是有意义的，所以子进程继承父进程的信号处理方式。<br>但是当子进程调用exec后，因为exec运行新的程序后会覆盖从父进程继承来的存储映像。<br>那么信号捕捉函数在新程序中已无意义，所以exec会将原先设置为要捕捉的信号都更改为默认动作。</p>
<ul>
<li>返回值<br>如果成功该函数返回零，否则返回非零。</li>
</ul>
<h3 id="signal"><a href="#signal" class="headerlink" title="signal"></a>signal</h3><ul>
<li><p>头文件:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;signal.h&gt;</span></span></span><br></pre></td></tr></table></figure>

</li>
<li><p>函数说明:<br>C 库函数,设置一个函数来处理信号，即带有 sig 参数的信号处理程序。</p>
</li>
<li><p>函数定义</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> (*signal(<span class="type">int</span> sig, <span class="type">void</span> (*func)(<span class="type">int</span>)))(<span class="type">int</span>)</span><br></pre></td></tr></table></figure></li>
</ul>
<p>参数说明：</p>
<ul>
<li><p>sig – 在信号处理程序中作为变量使用的信号码。下面是一些重要的标准信号常量</p>
</li>
<li><p>func – 一个指向函数的指针。它可以是一个由程序定义的函数，也可以是下面预定义函数之一。</p>
<ul>
<li>SIG_DFL	默认的信号处理程序。</li>
<li>SIG_IGN	忽视信号。</li>
</ul>
</li>
<li><p>返回值<br>该函数返回之前的信号处理程序</p>
</li>
<li><p>实例代码</p>
</li>
</ul>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;signal.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">signal_catchfunc</span><span class="params">(<span class="type">int</span>)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> ret;</span><br><span class="line">    signal(SIGINT, signal_catchfunc);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;开始生成一个信号\n&quot;</span>);</span><br><span class="line">    ret = raise(SIGINT);</span><br><span class="line">    <span class="keyword">if</span> (ret != <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;错误，不能生成SIGINT信号\n&quot;</span>);</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;退出....\n&quot;</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">signal_catchfunc</span><span class="params">(<span class="type">int</span> signal)</span> &#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;捕获信号\n&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="sandbox使用的信号"><a href="#sandbox使用的信号" class="headerlink" title="sandbox使用的信号"></a>sandbox使用的信号</h3><p>信号的共达60余个，这里只介绍一下sandbox中使用的信号。</p>
<h3 id="SIGUSR1-x2F-SIGUSR2"><a href="#SIGUSR1-x2F-SIGUSR2" class="headerlink" title="SIGUSR1&#x2F;SIGUSR2"></a>SIGUSR1&#x2F;SIGUSR2</h3><p>SIGUSR1 用户自定义信号 默认处理：进程终止;<br>SIGUSR2 用户自定义信号默认处理：进程终止。</p>
<h3 id="SIGSEGV"><a href="#SIGSEGV" class="headerlink" title="SIGSEGV"></a>SIGSEGV</h3><p>在POSIX兼容的平台上，SIGSEGV是当一个进程<strong>执行了一个无效的内存引用</strong>，或<strong>发生段错误</strong>时发送给它的信号。<br>SIGSEGV的符号常量在头文件<code>signal.h</code>中定义。<br>因为在不同平台上，信号数字可能变化，因此最好使用符号信号名。通常，它是信号#11。<br><a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/SIGSEGV">SIGSEGV维基百科</a></p>
<h2 id="execXX-函数组"><a href="#execXX-函数组" class="headerlink" title="execXX 函数组"></a>execXX 函数组</h2><p>exec函数族提供了一个在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件，<br>并用它来取代原调用进程的数据段、代码段和堆栈段，在执行完之后，原调用进程的内容除了进程号外，其他全部被新的进程替换了。<br>另外，这里的可执行文件既可以是二进制文件，也可以是Linux下任何可执行的脚本文件。</p>
<p>exec函数一共有六个，其中execve为内核级系统调用，<br>其他（execl，execle，execlp，execv，execvp）都是调用execve的库函数。<br>不同之处在于如何找到程序、如何指定参数以及环境来自何处。</p>
<ul>
<li><p>名称中带有 v 的调用，用带有一个数组参数来指定argv[]新程序的数组。数组的最后一个元素为 NULL。</p>
</li>
<li><p>名称中带有 l 的调用，将新程序的参数作为函数本身的可变长度参数列表。参数的结尾为参数指针(char *)NULL。<br>你应该始终进行类型转换，因为NULL实际是整数常量，并且调用可变参数函数时的默认参数转换不会将其转换为指针。</p>
</li>
<li><p>名称中带有 e 的调用，需要一个额外的参数（或l情况下的参数）来提供新程序的环境；否则，程序将继承当前进程的环境。</p>
</li>
</ul>
<h3 id="execve"><a href="#execve" class="headerlink" title="execve"></a>execve</h3><ul>
<li><p>头文件:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;unistd.h&gt;</span></span></span><br></pre></td></tr></table></figure>
</li>
<li><p>函数说明:<br>pthread_create是类Unix操作系统（Unix、Linux、Mac OS X等）的创建线程的函数。<br>它的功能是创建线程（实际上就是确定调用该线程函数的入口点），在线程创建以后，就开始运行相关的线程函数。</p>
</li>
<li><p>函数定义:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">execve</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *pathname, <span class="type">char</span> *<span class="type">const</span> argv[], <span class="type">char</span> *<span class="type">const</span> envp[])</span>;</span><br></pre></td></tr></table></figure>
</li>
<li><p>返回值:<br>如果执行成功则函数不会返回(后面的代码也并不会执行了)，执行失败则直接返回-1，失败原因存于errno 中。</p>
</li>
<li><p>示例代码:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @Author: 吉松阳</span></span><br><span class="line"><span class="comment"> * @Date: 2021/9/22</span></span><br><span class="line"><span class="comment"> * @Description: </span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">char</span> *argv[] = &#123;<span class="string">&quot;ls&quot;</span>, <span class="string">&quot;-el&quot;</span>, <span class="literal">NULL</span>&#125;;</span><br><span class="line">    <span class="type">char</span> *envp[] = &#123;<span class="string">&quot;PATH=/bin&quot;</span>, <span class="number">0</span>&#125;;</span><br><span class="line">    execve(<span class="string">&quot;/bin/ls&quot;</span>, argv, envp);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">  total 128</span></span><br><span class="line"><span class="comment">  drwxr-xr-x  5 jisongyang  staff    160 Sep 22 22:07 CMakeFiles</span></span><br><span class="line"><span class="comment">  -rw-r--r--  1 jisongyang  staff   6687 Sep 22 22:06 Makefile</span></span><br><span class="line"><span class="comment">  -rw-r--r--  1 jisongyang  staff   1021 Sep 22 22:06 cmake_install.cmake</span></span><br><span class="line"><span class="comment">  -rwxr-xr-x  1 jisongyang  staff  50424 Sep 22 22:22 test_execve</span></span><br><span class="line"><span class="comment"> */</span></span><br></pre></td></tr></table></figure></li>
</ul>
<p>参考链接:</p>
<ol>
<li><a target="_blank" rel="noopener" href="https://man7.org/linux/man-pages/man2/execve.2.html">execve(2) — Linux manual page</a></li>
<li><a target="_blank" rel="noopener" href="https://stackoverflow.com/questions/20823371/what-is-the-difference-between-the-functions-of-the-exec-family-of-system-calls">What is the difference between the functions of the exec family of system calls like exec and execve?</a></li>
</ol>
<h3 id="sleep"><a href="#sleep" class="headerlink" title="sleep"></a>sleep</h3><ul>
<li><p>头文件:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;unistd.h&gt;</span></span></span><br></pre></td></tr></table></figure>
</li>
<li><p>函数说明:<br>sleep()会令目前的进程暂停, 直到达到参数seconds 所指定的时间, 或是被信号所中断.</p>
</li>
<li><p>函数定义</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">unsigned</span> <span class="type">int</span> <span class="title function_">sleep</span><span class="params">(<span class="type">unsigned</span> <span class="type">int</span> seconds)</span>;</span><br></pre></td></tr></table></figure>
</li>
<li><p>返回值<br>若进程暂停到参数seconds 所指定的时间则返回0, 若有信号中断则返回剩余秒数.</p>
</li>
</ul>
<h2 id="wait-函数组"><a href="#wait-函数组" class="headerlink" title="wait 函数组"></a>wait 函数组</h2><p>进程调用 exit() 退出执行后，被设置为僵死状态。<br>这时父进程可以通过 wait4() 系统调用查询子进程是否终结，之后再进行最后的操作，彻底删除进程所占用的内存资源。<br>wait4() 系统调用由 linux 内核实现。<br>linux 系统通常提供了 wait()、waitpid()、wait3()、wait4() 这四个函数，<br>四个函数的参数不同，语义也有细微的差别，但是都返回关于终止进程的状态信息。</p>
<h3 id="wait"><a href="#wait" class="headerlink" title="wait"></a>wait</h3><ul>
<li><p>头文件:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/types.h&gt;</span>        <span class="comment">// 提供类型 pid_t 的定义</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/wait.h&gt;</span></span></span><br></pre></td></tr></table></figure>
</li>
<li><p>函数说明:<br>当进程调用 wait() 时，会暂停目前进程的执行（即阻塞），由 wait() 来自动分析是否当前进程的某个子进程已经退出，<br>如果找到了这样一个已经变成僵尸进程的子进程，wait 就会收集这个子进程的信息，并将其彻底销毁后返回；<br>如果没有找到这样一个子进程，wait 就会一直阻塞在这里，直到出现<strong>僵尸进程</strong>。</p>
</li>
<li><p>函数定义</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">pid_t</span> <span class="title function_">wait</span><span class="params">(<span class="type">int</span> *status)</span>;</span><br></pre></td></tr></table></figure>
<p>参数 status 保存着子进程退出时的一些状态（包括 task_struct、thread_info及内核栈等）它是一个指向 int 类型的指针；<br>如果不在意子进程的结束状态值，只想把这个僵尸进程消灭掉（实际上，大多数时候都是这样做的），则可以将这个参数设为 NULL。</p>
</li>
</ul>
<p>关于 status参数比较复杂，可以参考这里<br><a target="_blank" rel="noopener" href="https://www.ibm.com/developerworks/cn/linux/kernel/syscall/part3/index.html">传送门</a></p>
<ul>
<li>返回值<br>如果 wait() 调用成功，则会返回被收集子进程的进程ID；如果被调用进程没有子进程，则调用失败，返回 -1</li>
</ul>
<h3 id="waitpid"><a href="#waitpid" class="headerlink" title="waitpid"></a>waitpid</h3><ul>
<li><p>函数原型：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">pid_t</span> <span class="title function_">waitpid</span><span class="params">(<span class="type">pid_t</span> pid,<span class="type">int</span> *status,<span class="type">int</span> options)</span>;</span><br></pre></td></tr></table></figure>
<p>waitpid() 函数的功能与 wait() 的功能类似，不过，它比 wait() 函数多了两个参数：</p>
</li>
<li><p>1）参数 pid 为欲等待的子进程的识别码：<br>pid &lt; -1 ：等待进程组 ID 为 pid 绝对值的进程组中的任何子进程；<br>pid &#x3D; -1 ：等待任何子进程，此时 waitpid() 相当于 wait()。实际上，wait()就是 pid &#x3D; -1、options &#x3D; 0 的waitpid()， 且有：<br>pid &#x3D; 0 ：等待进程组 ID 与当前进程相同的任何子进程（也就是等待同一个进程组中的任何子进程）；<br>pid &gt; 0 ：等待任何子进程 ID 为 pid 的子进程，只要指定的子进程还没有结束，waitpid() 就会一直等下去。</p>
</li>
<li><p>2）参数 options 提供一些额外的选项来控制 waitpid()：<br>WNOHANG：如果没有任何已经结束了的子进程，则马上返回，不等待；<br>WUNTRACED：如果子进程进入暂停执行的情况，则马上返回，但结束状态不予理会；<br>也可以将这两个选项组合起来使用，使用 OR 操作。如果不想使用这两个选项，也可以直接把 options 设为 0 </p>
</li>
<li><p>3）waitpid() 的返回值，有三种：<br>a）正常返回时，waitpid() 返回收集到的子进程的PID；<br>b）如果设置了 WNOHANG，而调用 waitpid() 时，没有发现已退出的子进程可收集，则返回0；<br>c）如果调用出错，则返回 -1，这时erron 会被设置为相应的值以指示错误所在。（当 pid 所指示的子进程不错在，或此进程存在，但不是调用进程的子进程， waitpid() 就会返回出错，这时 erron 被设置为 ECHILD）</p>
</li>
</ul>
<h3 id="wait3-x2F-wait4"><a href="#wait3-x2F-wait4" class="headerlink" title="wait3&#x2F;wait4"></a>wait3&#x2F;wait4</h3><p>函数原型</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/tpyes.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/wait.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">pid_t</span> <span class="title function_">wait3</span><span class="params">(<span class="type">int</span> *status,<span class="type">int</span> options,<span class="keyword">struct</span> rusage *rusage)</span>;</span><br><span class="line"><span class="type">pid_t</span> <span class="title function_">wait4</span><span class="params">(<span class="type">pid_t</span> pid,<span class="type">int</span> *status,<span class="type">int</span> options,<span class="keyword">struct</span> rusage *rusage)</span>;</span><br></pre></td></tr></table></figure>
<p>wait3() 和 wait4() 函数除了可以获得子进程状态信息外，还可以获得子进程的资源使用信息，这些信息是通过参数 <strong>rusage</strong> 得到的。<br>而 wait3() 与 wait4() 之间的区别是，wait3() 等待所有进程，而 wait4() 可以根据 pid 的值选择要等待的子进程。<br>参数 pid 的意义与 waitpid() 函数的一样。</p>
<p>其中，需要注意的是，参数rusage所指向的结构中返回终止子进程的资源使用情况。其中包括进程使用的CPU时间总量以及内存管理的统计数据。<br>如下，为它的结构定义：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">rusage</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">timeval</span> <span class="title">ru_utime</span>;</span> <span class="comment">/* user CPU time used */</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">timeval</span> <span class="title">ru_stime</span>;</span> <span class="comment">/* system CPU time used */</span></span><br><span class="line">    <span class="type">long</span>   ru_maxrss;        <span class="comment">/* maximum resident set size */</span></span><br><span class="line">    <span class="type">long</span>   ru_ixrss;         <span class="comment">/* integral shared memory size */</span></span><br><span class="line">    <span class="type">long</span>   ru_idrss;         <span class="comment">/* integral unshared data size */</span></span><br><span class="line">    <span class="type">long</span>   ru_isrss;         <span class="comment">/* integral unshared stack size */</span></span><br><span class="line">    <span class="type">long</span>   ru_minflt;        <span class="comment">/* page reclaims (soft page faults) */</span></span><br><span class="line">    <span class="type">long</span>   ru_majflt;        <span class="comment">/* page faults (hard page faults) */</span></span><br><span class="line">    <span class="type">long</span>   ru_nswap;         <span class="comment">/* swaps */</span></span><br><span class="line">    <span class="type">long</span>   ru_inblock;       <span class="comment">/* block input operations */</span></span><br><span class="line">    <span class="type">long</span>   ru_oublock;       <span class="comment">/* block output operations */</span></span><br><span class="line">    <span class="type">long</span>   ru_msgsnd;        <span class="comment">/* IPC messages sent */</span></span><br><span class="line">    <span class="type">long</span>   ru_msgrcv;        <span class="comment">/* IPC messages received */</span></span><br><span class="line">    <span class="type">long</span>   ru_nsignals;      <span class="comment">/* signals received */</span></span><br><span class="line">    <span class="type">long</span>   ru_nvcsw;         <span class="comment">/* voluntary context switches */</span></span><br><span class="line">    <span class="type">long</span>   ru_nivcsw;        <span class="comment">/* involuntary context switches */</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>其中比较重要的三项：</p>
<ol>
<li>timeval ru_utime; &#x2F;* user CPU time used *&#x2F;        用户态CPU使用时间</li>
<li>timeval ru_stime; &#x2F;* system CPU time used *&#x2F;      内核态CPU使用时间</li>
<li>ru_maxrss;        &#x2F;* maximum resident set size *&#x2F; 最大驻留集</li>
</ol>
<p>还可以shell中使用</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">man getrusage</span><br></pre></td></tr></table></figure>

<h3 id="如何使用int-status"><a href="#如何使用int-status" class="headerlink" title="如何使用int* status"></a>如何使用int* status</h3><p>如果参数status的值不是NULL，wait就会把子进程退出时的状态取出并存入其中，这指向整数值（int），<br>指出了子进程是正常退出还是被非正常结束的，以及正常结束时的返回值，或被哪一个信号结束的等信息。<br>由于这些信息被存放在<strong>一个整数的不同二进制位</strong>中，所以用常规的方法读取会非常麻烦，<br>于是就设计了一套专门的宏来完成这项工作，下面介绍一下其中最常用的几个。</p>
<p>请注意，下面的宏中的 status 虽然名字一样，这里的参数status并不同于wait中的参数<br>一个是指向整数的指针，而是那个指针所指向的整数，切记不要搞混。</p>
<h3 id="WIFEXITED-int-status"><a href="#WIFEXITED-int-status" class="headerlink" title="WIFEXITED(int status)"></a>WIFEXITED(int status)</h3><p>这个宏用来指出进程是否为正常退出的，如果是，它会返回一个非零值。</p>
<h3 id="WEXITSTATUS-status"><a href="#WEXITSTATUS-status" class="headerlink" title="WEXITSTATUS(status)"></a>WEXITSTATUS(status)</h3><p><strong>当WIFEXITED返回非零值时</strong>，可以用这个宏来提取<strong>子进程的返回值</strong>。<br>如果子进程调用exit(5)退出，WEXITSTATUS(status) 就会返回5；如果子进程调用exit(7)，WEXITSTATUS(status)就会返回7。<br>请注意，如果进程不是正常退出的，也就是 说，WIFEXITED返回0，这个值就毫无意义。</p>
<p>所以二者往往这样配合使用：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//正常退出判断</span></span><br><span class="line"><span class="keyword">if</span>(WIFEXITED(status))&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;child exit with %d\n&quot;</span>, WEXITSTATUS(status));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="WIFSIGNALED-status"><a href="#WIFSIGNALED-status" class="headerlink" title="WIFSIGNALED(status)"></a>WIFSIGNALED(status)</h3><p>这个宏来指出进程是否遇到未处理的信号而意外退出，如果是，返回非零0.</p>
<h3 id=""><a href="#" class="headerlink" title=""></a></h3><p><strong>当 WIFSIGNALED 返回非零值时</strong>，此时可通过WTERMSIG(status)获取使得<strong>进程退出的信号编号</strong></p>
<p>所以二者可以这样配合使用</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//因为某种信号中断获取状态</span></span><br><span class="line"><span class="keyword">if</span>(WIFSIGNALED(status))&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;child killed by %d\n&quot;</span>, WTERMSIG(status));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<ul>
<li>参考链接<br><a target="_blank" rel="noopener" href="https://man7.org/linux/man-pages/man2/wait4.2.html">wait4(2) — Linux manual page</a></li>
</ul>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/11/20/Linux%E6%97%B6%E9%97%B4%E7%9B%B8%E5%85%B3%E5%BA%93%E5%87%BD%E6%95%B0/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="SongyangJi">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="JsyBlog">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | JsyBlog">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2021/11/20/Linux%E6%97%B6%E9%97%B4%E7%9B%B8%E5%85%B3%E5%BA%93%E5%87%BD%E6%95%B0/" class="post-title-link" itemprop="url">Linux时间相关库函数</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2021-11-20 22:10:55" itemprop="dateCreated datePublished" datetime="2021-11-20T22:10:55+08:00">2021-11-20</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">更新于</span>
      <time title="修改时间：2022-03-28 15:28:34" itemprop="dateModified" datetime="2022-03-28T15:28:34+08:00">2022-03-28</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/Linux/" itemprop="url" rel="index"><span itemprop="name">Linux</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h2 id="时间相关"><a href="#时间相关" class="headerlink" title="时间相关"></a>时间相关</h2><h3 id="time"><a href="#time" class="headerlink" title="time"></a>time</h3><ul>
<li>头文件:<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;time.h&gt;</span></span></span><br></pre></td></tr></table></figure></li>
<li>函数说明:<br>C 库函数: 返回自纪元 Epoch（1970-01-01 00:00:00 UTC）起经过的时间，以秒为单位。<br>如果 seconds 不为空，则返回值也存储在变量 seconds 中。</li>
<li>函数定义<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">time_t</span> <span class="title function_">time</span><span class="params">(<span class="type">time_t</span> *seconds)</span></span><br></pre></td></tr></table></figure></li>
<li>返回值<br>以 time_t 对象返回当前日历时间。</li>
</ul>
<h3 id="localtime"><a href="#localtime" class="headerlink" title="localtime"></a>localtime</h3><ul>
<li><p>头文件:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;time.h&gt;</span></span></span><br></pre></td></tr></table></figure>
</li>
<li><p>函数说明:<br>C 库函数  使用 timer 的值来填充 tm 结构。<br>timer 的值被分解为 tm 结构，并用本地时区表示。</p>
</li>
<li><p>函数定义</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> tm *<span class="title function_">localtime</span><span class="params">(<span class="type">const</span> <span class="type">time_t</span> *timer)</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>返回值<br>该函数返回指向 tm 结构的指针，该结构带有被填充的时间信息。下面是 tm 结构的细节:</p>
</li>
</ul>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">tm</span> &#123;</span></span><br><span class="line"><span class="type">int</span> tm_sec;         <span class="comment">/* 秒，范围从 0 到 59                */</span></span><br><span class="line"><span class="type">int</span> tm_min;         <span class="comment">/* 分，范围从 0 到 59                */</span></span><br><span class="line"><span class="type">int</span> tm_hour;        <span class="comment">/* 小时，范围从 0 到 23                */</span></span><br><span class="line"><span class="type">int</span> tm_mday;        <span class="comment">/* 一月中的第几天，范围从 1 到 31                    */</span></span><br><span class="line"><span class="type">int</span> tm_mon;         <span class="comment">/* 月份，范围从 0 到 11                */</span></span><br><span class="line"><span class="type">int</span> tm_year;        <span class="comment">/* 自 1900 起的年数                */</span></span><br><span class="line"><span class="type">int</span> tm_wday;        <span class="comment">/* 一周中的第几天，范围从 0 到 6                */</span></span><br><span class="line"><span class="type">int</span> tm_yday;        <span class="comment">/* 一年中的第几天，范围从 0 到 365                    */</span></span><br><span class="line"><span class="type">int</span> tm_isdst;       <span class="comment">/* 夏令时                        */</span>    </span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<h3 id="strftime"><a href="#strftime" class="headerlink" title="strftime"></a>strftime</h3><ul>
<li><p>头文件:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;time.h&gt;</span></span></span><br></pre></td></tr></table></figure>
</li>
<li><p>函数说明:<br>C 库函数,根据 format 中定义的格式化规则，格式化结构 timeptr 表示的时间，并把它存储在 str 中。</p>
</li>
<li><p>函数定义</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">size_t</span> <span class="title function_">strftime</span><span class="params">(<span class="type">char</span> *str, <span class="type">size_t</span> maxsize, <span class="type">const</span> <span class="type">char</span> *format, <span class="type">const</span> <span class="keyword">struct</span> tm *timeptr)</span></span><br></pre></td></tr></table></figure></li>
</ul>
<ul>
<li>str – 这是指向目标数组的指针，用来复制产生的 C 字符串。</li>
<li>maxsize – 这是被复制到 str 的最大字符数。</li>
<li>format – 这是 C 字符串，包含了普通字符和特殊格式说明符的任何组合。这些格式说明符由函数替换为表示 tm 中所指定时间的相对应值。<br>具体格式详见：<br><a target="_blank" rel="noopener" href="https://www.runoob.com/cprogramming/c-function-strftime.html">参考链接</a></li>
</ul>
<ul>
<li>返回值<br>如果产生的 C 字符串小于 size 个字符（包括空结束字符），则会返回复制到 str 中的字符总数（不包括空结束字符），否则返回零。</li>
</ul>
<h3 id="gettimeofday"><a href="#gettimeofday" class="headerlink" title="gettimeofday"></a>gettimeofday</h3><ul>
<li>头文件:<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/time.h&gt;</span></span></span><br></pre></td></tr></table></figure></li>
<li>函数说明:<br>返回当前距离1970年的秒数和微妙数，后面的tz是时区，一般不用（传 NULL 即可）。</li>
<li>函数定义<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">gettimeofday</span><span class="params">(<span class="keyword">struct</span> timeval *tv, <span class="keyword">struct</span> timezone *tz)</span>;</span><br></pre></td></tr></table></figure></li>
</ul>
<h3 id="clock-gettime"><a href="#clock-gettime" class="headerlink" title="clock_gettime"></a>clock_gettime</h3><ul>
<li><p>头文件:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;time.h&gt;</span></span></span><br></pre></td></tr></table></figure>
</li>
<li><p>函数说明:<br>根据时钟模式，获取多种时间。</p>
</li>
<li><p>函数定义</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">clock_gettime</span><span class="params">(<span class="type">clockid_t</span> clock_id, <span class="keyword">struct</span> timespec * tp )</span>;</span><br></pre></td></tr></table></figure>
</li>
<li><p>CLOCK_REALTIME       0<br>Systemwide realtime clock. 系统实时时间,随系统实时时间改变而改变。<br>即从UTC1970-1-1 0:0:0开始计时,中间时刻如果系统时间被用户该成其他,则对应的时间相应改变</p>
</li>
<li><p>CLOCK_MONOTONIC     1<br>Represents monotonic time. Cannot be set. 从系统启动这一刻起开始计时,不受系统时间被用户改变的影响<br>用的是相对时间，它的时间是通过jiffies值来计算的。该时钟不受系统时钟源的影响，只受jiffies值的影响。<br>也就是说它获得的时间戳是单调的。</p>
</li>
<li><p>CLOCK_PROCESS_CPUTIME_ID    2<br>High resolution per-process timer. 本进程到当前代码系统CPU花费的时间</p>
</li>
<li><p>CLOCK_THREAD_CPUTIME_ID      3<br>Thread-specific timer. 本线程到当前代码系统CPU花费的时间</p>
</li>
<li><p>CLOCK_REALTIME_HR                4<br>High resolution version of CLOCK_REALTIME. 0<br>CLOCK_REALTIME 的 高精度版本</p>
</li>
<li><p>CLOCK_MONOTONIC_HR            5<br>High resolution version of CLOCK_MONOTONIC.<br>CLOCK_MONOTONIC 的高精度版本</p>
</li>
<li><p>返回值<br>时间结构<code>struct timespec</code></p>
</li>
<li><p>示例代码</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/time.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">timeval</span> <span class="title">tv</span>;</span></span><br><span class="line">    gettimeofday(&amp;tv, <span class="literal">NULL</span>);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;gettimeofday : %ld, %d\n&quot;</span>, tv.tv_sec,tv.tv_usec);</span><br><span class="line"></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">timespec</span> <span class="title">ts</span>;</span></span><br><span class="line">    clock_gettime(CLOCK_REALTIME, &amp;ts);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;CLOCK_REALTIME: %ld, %ld\n&quot;</span>, ts.tv_sec, ts.tv_nsec);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//打印出来的时间跟 cat /proc/uptime 第一个参数一样</span></span><br><span class="line">    clock_gettime(CLOCK_MONOTONIC, &amp;ts);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;CLOCK_MONOTONIC: %ld, %ld\n&quot;</span>, ts.tv_sec, ts.tv_nsec);</span><br><span class="line"></span><br><span class="line">    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &amp;ts);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;CLOCK_PROCESS_CPUTIME_ID: %ld, %ld\n&quot;</span>, ts.tv_sec, ts.tv_nsec);</span><br><span class="line"></span><br><span class="line">    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &amp;ts);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;CLOCK_THREAD_CPUTIME_ID: %ld, %ld\n&quot;</span>, ts.tv_sec, ts.tv_nsec);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;\n%ld\n&quot;</span>, time(<span class="literal">NULL</span>));</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
</ul>
<p>值得一提的是，本项目使用的计时工具不能使用 time 以及 gettimeofday，否则有小概率发生”时间回溯现象”，<br>具体可以参考 <a target="_blank" rel="noopener" href="https://song-yang-ji.blog.csdn.net/article/details/115837363">Linux的timedatectl —— 关闭或开启时间同步</a>.<br>必须使用 <code>clock_gettime(CLOCK_MONOTONIC, *timespec);</code> 才可以先后两次拿到的时间戳是递增的。</p>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/11/20/Linux%E8%AF%BB%E5%86%99%E6%96%87%E4%BB%B6%E7%9B%B8%E5%85%B3%E5%BA%93%E5%87%BD%E6%95%B0/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="SongyangJi">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="JsyBlog">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | JsyBlog">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2021/11/20/Linux%E8%AF%BB%E5%86%99%E6%96%87%E4%BB%B6%E7%9B%B8%E5%85%B3%E5%BA%93%E5%87%BD%E6%95%B0/" class="post-title-link" itemprop="url">Linux读写文件相关库函数</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2021-11-20 22:07:03" itemprop="dateCreated datePublished" datetime="2021-11-20T22:07:03+08:00">2021-11-20</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">更新于</span>
      <time title="修改时间：2022-03-28 15:28:00" itemprop="dateModified" datetime="2022-03-28T15:28:00+08:00">2022-03-28</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/Linux/" itemprop="url" rel="index"><span itemprop="name">Linux</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h2 id="读写文件相关"><a href="#读写文件相关" class="headerlink" title="读写文件相关"></a>读写文件相关</h2><h3 id="write"><a href="#write" class="headerlink" title="write"></a>write</h3><ul>
<li><p>头文件:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;unistd.h&gt;</span></span></span><br></pre></td></tr></table></figure>
</li>
<li><p>函数说明:<br>write系统调用，是把缓存区buf中的前nbytes字节写入到与文件描述符有关的文件中。</p>
</li>
<li><p>函数定义</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//参数分别为 文件描述符、缓冲区、</span></span><br><span class="line"><span class="type">size_t</span> <span class="title function_">write</span><span class="params">(<span class="type">int</span> flides, <span class="type">const</span> <span class="type">void</span> *buf, <span class="type">size_t</span> nbytes)</span>;</span><br></pre></td></tr></table></figure>
</li>
<li><p>返回值<br>write系统调用返回的是实际写入到文件中的字节数。</p>
</li>
</ul>
<h3 id="read"><a href="#read" class="headerlink" title="read"></a>read</h3><ul>
<li><p>头文件:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;unistd.h&gt;</span></span></span><br></pre></td></tr></table></figure>
</li>
<li><p>函数说明:<br>read系统调用，是从与文件描述符flides相关联的文件中读取前nbytes字节的内容，并且写入到数据区buf中。</p>
</li>
<li><p>函数定义</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">size_t</span> <span class="title function_">read</span><span class="params">(<span class="type">int</span> flides, <span class="type">void</span> *buf, <span class="type">size_t</span> nbytes)</span>;</span><br></pre></td></tr></table></figure>
</li>
<li><p>返回值<br>read系统调用返回的是实际读入的字节数。</p>
</li>
</ul>
<h3 id="open"><a href="#open" class="headerlink" title="open"></a>open</h3><ul>
<li><p>头文件:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;fcntl.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/types.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/stat.h&gt;</span></span></span><br></pre></td></tr></table></figure>
</li>
<li><p>函数说明:</p>
</li>
<li><p>函数定义</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">open</span><span class="params">(<span class="type">const</span> *path, <span class="type">int</span> oflags)</span>; <span class="comment">//1</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">open</span><span class="params">(<span class="type">const</span> *path, <span class="type">int</span> oflags, <span class="type">mode_t</span> mode)</span>; <span class="comment">//2</span></span><br></pre></td></tr></table></figure></li>
</ul>
<p>参数说明：<br>其中，oflags是由必需文件访问模式和可选模式一起构成的(通过按位或“|”)：<br>必需部分：</p>
<ul>
<li>O_RDONLY———-以只读方式打开</li>
<li>O_WRONLY———以只写方式打开</li>
<li>O_RDWR————以读写方式打开<br>可选部分：</li>
<li>O_CREAT————按照参数mode给出的访问模式创建文件</li>
<li>O_EXCL————–与O_CREAT一起使用，确保创建出文件，避免两个程序同时创建同一个文件，如文件存在则open调用失败 </li>
<li>O_APPEND———-把写入数据追加在文件的末尾</li>
<li>O_TRUNC———–把文件长度设置为0，丢弃原有内容</li>
</ul>
<p>在第一种调用方式上，加上了第三个参数mode，<strong>主要是搭配O_CREAT使用</strong>，同样地，这个参数规定了属主、同组和其他人对文件的文件操作权限。</p>
<ul>
<li>文件属主<ul>
<li>S_IRUSR———-读权限 </li>
<li>S_IWUSR———写权限</li>
<li>S_IXUSR———-执行权限</li>
</ul>
</li>
<li>文件所属组  <ul>
<li>S_IRGRP———-读权限 </li>
<li>S_IWGRP———写权限 </li>
<li>S_IXGRP———-执行权限</li>
</ul>
</li>
<li>其他人  <ul>
<li>S_IROTH———-读权限 </li>
<li>S_IWOTH———写权限</li>
<li>S_IXOTH———-执行权限</li>
</ul>
</li>
</ul>
<p>另外，也可以用数字设定法：<br>0 : 无权限；<br>1 : 只执行；<br>2 : 只写；<br>4 : 只读。</p>
<p>这种权限设计实际上就是linux文件权限的设计。</p>
<ul>
<li>返回值<br>open系统调用成功返回一个新的文件描述符，失败返回-1。</li>
</ul>
<h3 id="close"><a href="#close" class="headerlink" title="close"></a>close</h3><ul>
<li><p>函数定义</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="type">int</span> <span class="title function_">close</span><span class="params">(<span class="type">int</span> flides)</span>;</span><br></pre></td></tr></table></figure>
</li>
<li><p>函数说明:<br>终止文件描述符flides与其对应的文件间的联系，文件描述符被释放，可重新使用。<br>使用完文件描述符之后，要记得释放！</p>
</li>
</ul>
<h3 id="fopen"><a href="#fopen" class="headerlink" title="fopen"></a>fopen</h3><ul>
<li><p>头文件:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br></pre></td></tr></table></figure></li>
<li><p>函数说明:<br>C 库函数,使用给定的模式 mode 打开 filename 所指向的文件。</p>
</li>
<li><p>函数定义</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">FILE *<span class="title function_">fopen</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *filename, <span class="type">const</span> <span class="type">char</span> *mode)</span></span><br></pre></td></tr></table></figure>
<table>
<thead>
<tr>
<th>“r”</th>
<th>打开一个用于读取的文件。该文件必须存在。</th>
</tr>
</thead>
<tbody><tr>
<td>“w”</td>
<td>创建一个用于写入的空文件。如果文件名称与已存在的文件相同，则会删除已有文件的内容，文件被视为一个新的空文件。</td>
</tr>
<tr>
<td>“a”</td>
<td>追加到一个文件。写操作向文件末尾追加数据。如果文件不存在，则创建文件。</td>
</tr>
<tr>
<td>“r+”</td>
<td>打开一个用于更新的文件，可读取也可写入。该文件必须存在。</td>
</tr>
<tr>
<td>“w+”</td>
<td>创建一个用于读写的空文件。</td>
</tr>
<tr>
<td>“a+”</td>
<td>打开一个用于读取和追加的文件。</td>
</tr>
</tbody></table>
</li>
<li><p>返回值<br>该函数返回一个 FILE 指针。否则返回 NULL，且设置全局变量 errno 来标识错误。</p>
</li>
</ul>
<h3 id="fclose"><a href="#fclose" class="headerlink" title="fclose"></a>fclose</h3><ul>
<li><p>头文件:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br></pre></td></tr></table></figure></li>
<li><p>函数说明:<br>C 库函数 int fclose(FILE *stream) 关闭流 stream, 并且刷新所有的缓冲区。</p>
</li>
<li><p>函数定义</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// stream -- 这是指向 FILE 对象的指针，该 FILE 对象指定了要被关闭的流。</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">fclose</span><span class="params">(FILE *stream)</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>返回值<br>如果流成功关闭，则该方法返回零。如果失败，则返回 EOF。</p>
</li>
</ul>
<h3 id="fprintf"><a href="#fprintf" class="headerlink" title="fprintf"></a>fprintf</h3><ul>
<li><p>头文件:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br></pre></td></tr></table></figure></li>
<li><p>函数说明:<br>C 库函数, 发送格式化输出到流 stream 中。</p>
</li>
<li><p>函数定义</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">fprintf</span><span class="params">(FILE *stream, <span class="type">const</span> <span class="type">char</span> *format, ...)</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>返回值<br>如果成功，则返回写入的字符总数，否则返回一个负数。</p>
</li>
</ul>
<h3 id="flock"><a href="#flock" class="headerlink" title="flock"></a>flock</h3><ul>
<li><p>头文件 </p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/file.h&gt;</span></span></span><br></pre></td></tr></table></figure>
</li>
<li><p>函数说明 flock()会依参数operation所指定的方式对参数fd所指的文件做各种锁定或解除锁定的动作。<br>此函数只能锁定整个文件，无法锁定文件的某一区域。</p>
</li>
<li><p>函数定义</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// fd 文件描述符、 锁定operation</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">flock</span><span class="params">(<span class="type">int</span> fd,<span class="type">int</span> operation)</span>;</span><br></pre></td></tr></table></figure></li>
</ul>
<p>参数 operation 有下列四种情况:</p>
<ul>
<li>LOCK_SH 建立共享锁定。多个进程可同时对同一个文件作共享锁定。</li>
<li>LOCK_EX 建立互斥锁定。一个文件同时只有一个互斥锁定。</li>
<li>LOCK_UN 解除文件锁定状态。</li>
<li>LOCK_NB 无法建立锁定时，此操作可不被阻断，马上返回进程。(通常与LOCK_SH或LOCK_EX做OR组合)</li>
</ul>
<ul>
<li>返回值<br>返回0表示成功，若有错误则返回-1，错误代码存于errno。</li>
</ul>
<h3 id="snprintf"><a href="#snprintf" class="headerlink" title="snprintf"></a>snprintf</h3><ul>
<li><p>头文件:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br></pre></td></tr></table></figure>
</li>
<li><p>函数说明:<br>C 库函数，将可变参数(…)按照 format 格式化成字符串，并将字符串复制到 str 中，size 为要写入的字符的最大数目，超过 size 会被截断。</p>
</li>
<li><p>函数定义</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">snprintf</span> <span class="params">(<span class="type">char</span> * str, <span class="type">size_t</span> size, <span class="type">const</span> <span class="type">char</span> * format, ... )</span>;</span><br></pre></td></tr></table></figure>

<ul>
<li>str – 目标字符串。</li>
<li>size – 拷贝字节数(Bytes)。</li>
<li>format – 格式化成字符串。</li>
<li>… – 可变参数。</li>
</ul>
</li>
<li><p>返回值</p>
<ul>
<li>如果格式化后的字符串长度小于等于 size，则会把字符串全部复制到 str 中，并给其后添加一个字符串结束符 \0。<br>返回的实际写入的长度。</li>
<li>如果格式化后的字符串长度大于 size，超过 size 的部分会被截断，只将其中的 (size-1) 个字符复制到 str 中，并给其后添加一个字符串结束符 \0。<br>返回值为欲写入的字符串长度。</li>
</ul>
</li>
</ul>
<h2 id="重定向"><a href="#重定向" class="headerlink" title="重定向"></a>重定向</h2><h3 id="dup"><a href="#dup" class="headerlink" title="dup"></a>dup</h3><ul>
<li><p>头文件:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;unistd.h&gt;</span></span></span><br></pre></td></tr></table></figure>
</li>
<li><p>函数说明:<br>dup用来复制参数oldfd所指的文件描述符。<br>返回的新文件描述符和参数oldfd指向同一个文件，这<strong>两个描述符共享同一个数据结构，共享所有的锁定，读写指针和各项标志位</strong>。</p>
</li>
<li><p>函数定义</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">dup</span><span class="params">(<span class="type">int</span> oldfd)</span>;</span><br></pre></td></tr></table></figure>
</li>
<li><p>返回值<br>当复制成功是，返回最小的尚未被使用过的文件描述符;<br>若有错误则返回-1。<br>错误代码存入errno中。</p>
</li>
</ul>
<h3 id="dup2"><a href="#dup2" class="headerlink" title="dup2"></a>dup2</h3><ul>
<li><p>头文件:</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;unistd.h&gt;</span></span></span><br></pre></td></tr></table></figure>
</li>
<li><p>函数说明:<br>dup2与dup区别是dup2可以用参数newfd指定新文件描述符的数值。<br>若参数newfd已经被程序使用，则系统就会将newfd所指的文件关闭；<br>若newfd等于oldfd，则返回newfd,而不关闭newfd所指的文件。<br>dup2所复制的文件描述符与原来的文件描述符共享各种文件状态。共享所有的锁定，读写位置和各项权限或flags等.<br>在shell的重定向功能中，(输入重定向”&lt;”和输出重定向”&gt;”)就是通过调用dup或dup2函数对标准输入和标准输出的操作来实现的。</p>
</li>
<li><p>函数定义</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">dup2</span><span class="params">(<span class="type">int</span> oldfd, <span class="type">int</span> newfd)</span>;</span><br></pre></td></tr></table></figure>
</li>
<li><p>返回值<br>若dup2调用成功则返回新的文件描述符，出错则返回-1。</p>
</li>
<li><p>举例：<br>如何使用dup2实现标准输出到文件的重定向？</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;fcntl.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;string.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> oldfd;</span><br><span class="line">    <span class="type">int</span> fd;</span><br><span class="line">    <span class="type">int</span> t;</span><br><span class="line">    <span class="type">char</span> *buf = <span class="string">&quot;This is a test!!!!\n&quot;</span>;</span><br><span class="line">    <span class="keyword">if</span> ((oldfd = open(<span class="string">&quot;/Users/jisongyang/CLionProjects/test_syscalls_sandbox/redirect/mine.txt&quot;</span>, O_RDWR | O_CREAT,</span><br><span class="line">                      <span class="number">0644</span>)) == <span class="number">-1</span>) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;open error\n&quot;</span>);</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">-1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    fd = dup2(oldfd, STDOUT_FILENO);</span><br><span class="line">    <span class="keyword">if</span> (fd == <span class="number">-1</span>) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;dup2 error\n&quot;</span>);</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">-1</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;fd:%d  STDOUT_FILENO:%d\n&quot;</span>, fd, STDOUT_FILENO);</span><br><span class="line">    &#125;</span><br><span class="line">    t = (<span class="type">int</span>) <span class="built_in">strlen</span>(buf);</span><br><span class="line">    <span class="keyword">if</span> (write(fileno(<span class="built_in">stdout</span>), buf, t) != t)<span class="comment">//本应该写入到stdout的信息，但是标准输出已经重定向到目标文件中，故向标准输出写的数据将会写到目标文件中。</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;write error!\n&quot;</span>);</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">-1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    fflush(<span class="built_in">stdout</span>); <span class="comment">// printf 是带缓冲的函数，不加这一行代码，printf 的内容不会写到文件里</span></span><br><span class="line">    close(fd);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
</ul>
<p>最重要的一行代码：<code>dup2(oldfd, STDOUT_FILENO)</code></p>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/11/20/socket/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="SongyangJi">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="JsyBlog">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | JsyBlog">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2021/11/20/socket/" class="post-title-link" itemprop="url">Socket编程 —— 使用 select、poll、epoll</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2021-11-20 20:27:00" itemprop="dateCreated datePublished" datetime="2021-11-20T20:27:00+08:00">2021-11-20</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">更新于</span>
      <time title="修改时间：2022-03-17 03:14:33" itemprop="dateModified" datetime="2022-03-17T03:14:33+08:00">2022-03-17</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/IO/" itemprop="url" rel="index"><span itemprop="name">IO</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h2 id="socket编程"><a href="#socket编程" class="headerlink" title="socket编程"></a>socket编程</h2><img src="socket-state.png" style="zoom:50%;" />



<ol>
<li><p>服务端和客户端初始化 socket ，得到⽂件描述符；</p>
</li>
<li><p>服务端调⽤ bind ，将绑定在 IP 地址和端⼝;</p>
</li>
<li><p>服务端调⽤ listen ，进⾏监听；</p>
</li>
<li><p>服务端调⽤ accept ，等待客户端连接；</p>
</li>
<li><p>客户端调⽤ connect ，向服务器端的地址和端⼝发起连接请求；</p>
</li>
<li><p>服务端 accept 返回⽤于传输的 socket 的⽂件描述符；</p>
</li>
<li><p>客户端调⽤ write 写⼊数据；服务端调⽤ read 读取数据；</p>
</li>
<li><p>客户端断开连接时，会调⽤ close ，那么服务端 read 读取数据的时候，就会读取到了 EOF ，待处理完</p>
</li>
</ol>
<p>数据后，服务端调⽤ close ，表示连接关闭。</p>
<p>这⾥需要注意的是，服务端调⽤ accept 时，连接成功了会返回⼀个已完成连接的 socket，后续⽤来传输数据。</p>
<p>所以，监听的 socket 和真正⽤来传送数据的 socket，是两个 socket，⼀个叫作<strong>监听 socket</strong>，⼀个叫作<strong>已完成连接 socket</strong>。</p>
<p>成功连接建⽴之后，双⽅开始通过 read 和 write 函数来读写数据，就像往⼀个⽂件流⾥⾯写东⻄⼀样。</p>
<h2 id="select函数"><a href="#select函数" class="headerlink" title="select函数"></a>select函数</h2><p>select 实现多路复⽤的⽅式是，<strong>将已连接的 Socket 都放到⼀个⽂件描述符集合</strong>，</p>
<p>然后<strong>调⽤ select 函数将⽂件描述符集合拷⻉到内核⾥，让内核来检查是否有⽹络事件产⽣</strong>，检查的⽅式很粗暴，就是通过<strong>遍历⽂件描述符集合的⽅式</strong>，<strong>当检查到有事件产⽣后，将此 Socket 标记为可读或可写， 接着再把整个⽂件描述符集合拷⻉回⽤户态⾥</strong>，然后⽤户态还需要<strong>再通过遍历的⽅法找到可读或可写的 Socke</strong>t，然后再对其处</p>
<p>理。</p>
<p>所以，对<strong>于 select 这种⽅式，需要进⾏ 2 次「遍历」⽂件描述符集合</strong>，⼀次是在内核态⾥，⼀个次是在<strong>⽤户态</strong> ，⽽且还会发⽣ <strong>2</strong> 次「拷⻉」⽂件描述符集合，先从⽤户空间传⼊内核空间，由内核修改后，再传出到⽤户空间中。</p>
<p>select 使⽤固定⻓度的 BitsMap，表示⽂件描述符集合，⽽且<strong>所⽀持的⽂件描述符的个数是有限制的</strong>，在</p>
<p>Linux 系统中，由内核中的 FD_SETSIZE 限制， 默认最⼤值为 1024 ，只能监听 0~1023 的⽂件描述符。</p>
<h2 id="poll-函数"><a href="#poll-函数" class="headerlink" title="poll 函数"></a>poll 函数</h2><p><strong>poll 不再⽤位图或数组来存储所关注的⽂件描述符，取⽽代之⽤动态数组</strong>，以<strong>链表形式来组织，突破了</strong></p>
<p><strong>select 的⽂件描述符个数限制</strong>，当然还会受到系统⽂件描述符限制。</p>
<p>但是 poll 和 select 并没有太⼤的本质区别，都是使⽤「线性结构」存储进程关注的 <strong>Socket</strong> 集合，因此<strong>都需要遍历⽂件描述符集合</strong>来找到可读或可写的 <strong>Socket</strong>，时间复杂度为 **O(n)**，</p>
<p>⽽且<strong>也需要在⽤户态与内核态之间拷⻉⽂件描述符集合</strong>，这种⽅式随着并发数上来，性能的损耗会呈指数级增⻓。</p>
<h2 id="epoll-函数"><a href="#epoll-函数" class="headerlink" title="epoll 函数"></a>epoll 函数</h2><p>epoll 通过两个⽅⾯，很好解决了 select&#x2F;poll 的问题。</p>
<ul>
<li>第⼀点，epoll 在内核⾥<strong>使⽤红⿊树来跟踪进程所有待检测的⽂件描述符</strong>，把需要监控的 socket 通过</li>
</ul>
<p><code>epoll_ctl()</code> 函数加⼊内核中的红⿊树⾥，红⿊树是个⾼效的数据结构，增删查⼀般时间复杂度是</p>
<p>O(logn) ，通过对这棵⿊红树进⾏操作，<strong>这样就不需要像 select&#x2F;poll 每次操作时都传⼊整个 socket 集合，只需要传⼊⼀个待检测的 socket，减少了内核和⽤户空间⼤量的数据拷⻉和内存分配</strong>。</p>
<ul>
<li>第⼆点， epoll 使⽤<strong>事件驱动</strong>的机制，<strong>内核⾥维护了⼀个链表来记录就绪事件</strong>，<strong>当某个 socket 有事件发⽣时，通过回调函数内核会将其加⼊到这个就绪事件列表中</strong>，当⽤户调⽤ <code>epoll_wait() </code>函数时，<strong>只会返回有事件发⽣的⽂件描述符的个数，不需要像 select&#x2F;poll 那样轮询扫描整个 socket 集合</strong>，⼤⼤提⾼了检测的效率。<br><img src="/2021/11/20/socket/epoll.png"></li>
</ul>
<h3 id="相关接口"><a href="#相关接口" class="headerlink" title="相关接口"></a>相关接口</h3><p>epoll API是Linux专有的特性，相较于<code>select</code>和<code>poll</code>，<code>epoll</code>更加灵活且<strong>没有描述符限制</strong>。<code>epoll</code>设计也与<code>select</code>和<code>poll</code>不同，主要包含以下三个接口：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">epoll_create</span><span class="params">(<span class="type">int</span> size)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">epoll_ctl</span><span class="params">(<span class="type">int</span> epfd, <span class="type">int</span> op, <span class="type">int</span> fd, <span class="keyword">struct</span> epoll_event *event)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">epoll_wait</span><span class="params">(<span class="type">int</span> epfd, <span class="keyword">struct</span> epoll_event * events, <span class="type">int</span> maxevents, <span class="type">int</span> timeout)</span>;</span><br></pre></td></tr></table></figure>


<blockquote>
<p>下面依次介绍。</p>
</blockquote>
<h4 id="epoll-create"><a href="#epoll-create" class="headerlink" title="epoll_create()"></a>epoll_create()</h4><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">epoll_create</span><span class="params">(<span class="type">int</span> size)</span>；<span class="comment">//创建一个epoll的句柄，size用来告诉内核这个监听的数目一共有多大</span></span><br></pre></td></tr></table></figure>

<p>创建一个epoll的句柄，size用来告诉内核这个监听的数目一共有多大，这个参数不同于select()中的第一个参数，给出最大监听的fd+1的值，<em>参数size并不是限制了epoll所能监听的描述符最大个数，只是对内核初始分配内部数据结构的一个建议</em>。<br>当创建好epoll句柄后，它就会占用一个fd值，在linux下如果查看&#x2F;proc&#x2F;进程id&#x2F;fd&#x2F;，是能够看到这个fd的，所以在使用完epoll后，必须调用close()关闭，否则可能导致fd被耗尽。</p>
<h4 id="int-epoll-ctl"><a href="#int-epoll-ctl" class="headerlink" title="int epoll_ctl()"></a>int epoll_ctl()</h4><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">epoll_ctl</span><span class="params">(<span class="type">int</span> epfd, <span class="type">int</span> op, <span class="type">int</span> fd, <span class="keyword">struct</span> epoll_event *ev)</span>; <span class="comment">//修改兴趣列表（事件注册函数）</span></span><br></pre></td></tr></table></figure>

<p>该函数是对指定描述符fd执行op操作。</p>
<ul>
<li>epfd：是epoll_create()的返回值。</li>
<li>op：表示op操作，用三个宏来表示：1. <code>EPOLL_CTL_ADD</code>注册新的fd到epfd中；2. <code>EPOLL_CTL_MOD</code>修改已经注册的fd的监听事件；3. <code>EPOLL_CTL_DEL</code>从<code>epfd</code>中删除一个fd。</li>
<li>fd：是需要监听的fd（文件描述符）</li>
<li>epoll_event：是告诉内核需要监听什么事，struct epoll_event结构如下：</li>
</ul>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">epoll_event</span> &#123;</span></span><br><span class="line">  <span class="type">__uint32_t</span> events;  <span class="comment">/* Epoll events */</span></span><br><span class="line">  <span class="type">epoll_data_t</span> data;  <span class="comment">/* User data variable */</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p><strong>events可以是以下几个宏的集合</strong>：</p>
<ol>
<li>EPOLLIN ：表示对应的文件描述符可以读（包括对端SOCKET正常关闭）；</li>
<li>EPOLLOUT：表示对应的文件描述符可以写；</li>
<li>EPOLLPRI：表示对应的文件描述符有紧急的数据可读（这里应该表示有带外数据到来）；</li>
<li>EPOLLERR：表示对应的文件描述符发生错误；</li>
<li>EPOLLHUP：表示对应的文件描述符被挂断；</li>
<li>EPOLLET： bsEPOLL设为边缘触发（Edge Triggered)模式，这是相对于水平触发(Level Triggered）来说的。</li>
<li>EPOLLONESHOT：只监听一次事件，当监听完这次事件之后，如果还需要继续监听这个socket的话，需要再次把这个socket加入到EPOLL队列里。</li>
</ol>
<p>其中<code>data</code>的类型为：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">union</span> <span class="title">epoll_data</span> &#123;</span></span><br><span class="line">  <span class="type">void</span>    *ptr; <span class="comment">//pointer to user defined data</span></span><br><span class="line">  <span class="type">int</span>     fd; <span class="comment">//file descriptor</span></span><br><span class="line">  uint_32 u32; <span class="comment">//32-bit integer</span></span><br><span class="line">  uint_64 u64; <span class="comment">//64-bit integer</span></span><br><span class="line">&#125; <span class="type">epoll_data_t</span>;</span><br></pre></td></tr></table></figure>

<p><strong><code>data</code>字段是唯一可以获知同这个事件相关的文件描述符的途径</strong>，因此调用<code>epoll_ctl()</code>将文件描述符添加到兴趣列表中时，应该要么将<code>ev.data.fd</code>设为文件描述符，要么将<code>ev.data.ptr</code>设为指向包含该文件描述的结构体。</p>
<h4 id="int-epoll-wait"><a href="#int-epoll-wait" class="headerlink" title="int epoll_wait()"></a>int epoll_wait()</h4><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">epoll_wait</span><span class="params">(<span class="type">int</span> epfd, <span class="keyword">struct</span> epoll_event *evlist, <span class="type">int</span> maxevents, <span class="type">int</span> timeout)</span>;</span><br></pre></td></tr></table></figure>

<p>数组<code>evlist</code>的空间由调用者负责申请;</p>
<p>等待epfd上的io事件，最多返回maxevents个事件。<br>参数evlist用来从内核得到事件的集合，maxevents告之内核这个evlist有多大，这个maxevents的值不能大于创建epoll_create()时的size，参数timeout是超时时间（毫秒，0会立即返回，-1将不确定，也有说法说是永久阻塞）。<strong>该函数返回需要处理的事件数目，如返回0表示已超时</strong>。</p>
<h3 id="epoll的工作模式"><a href="#epoll的工作模式" class="headerlink" title="epoll的工作模式"></a>epoll的工作模式</h3><p>epoll对文件描述符的操作有两种模式：<strong>LT（level trigger）</strong>和<strong>ET（edge trigger）</strong>。LT模式是默认模式，LT模式与ET模式的区别如下：</p>
<p><strong>LT模式</strong>：当epoll_wait检测到描述符事件发生并将此事件通知应用程序，<strong>应用程序可以不立即处理该事件</strong>。下次调用epoll_wait时，会再次响应应用程序并通知此事件。</p>
<p><strong>ET模式</strong>：当epoll_wait检测到描述符事件发生并将此事件通知应用程序，<strong>应用程序必须立即处理该事件</strong>。如果不处理，下次调用epoll_wait时，不会再次响应应用程序并通知此事件。</p>
<p>LT模式下，<strong>主要缓冲区数据一次没有处理完，那么下次<code>epoll_wait</code>返回时，还会返回这个句柄</strong>；</p>
<p>而ET模式下，缓冲区数据一次没处理结束，那么下次就不会再通知了，只在第一次返回．所以在ET模式下，一般是通过while循环，一次性读完全部数据<strong>．epoll默认使用的是LT</strong>．</p>
<h4 id="1-LT模式"><a href="#1-LT模式" class="headerlink" title="1. LT模式"></a>1. LT模式</h4><p>LT(level triggered)是默认的工作方式，并且<strong>同时支持block和no-block</strong> socket.在这种做法中，内核告诉你一个文件描述符是否就绪了，然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作，内核还是会继续通知你的。</p>
<h4 id="2-ET模式"><a href="#2-ET模式" class="headerlink" title="2. ET模式"></a>2. ET模式</h4><p><strong>ET(edge-triggered)是高速工作方式</strong>，只支持no-block socket。在这种模式下，当描述符从未就绪变为就绪时，内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪，并且不会再为那个文件描述符发送更多的就绪通知，直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如，你在发送，接收或者接收请求，或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误）。但是请注意，如果一直不对这个fd作IO操作(从而导致它再次变成未就绪)，内核不会发送更多的通知(only once)</p>
<p><strong>ET模式在很大程度上减少了epoll事件被重复触发的次数</strong>，因此效率要比LT模式高。epoll工作在ET模式的时候，<strong>必须使用非阻塞套接口</strong>，以避免由于一个文件句柄的阻塞读&#x2F;阻塞写操作把处理多个文件描述符的任务饿死。</p>
<p><strong>假如有这样一个例子：</strong></p>
<ol>
<li>我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符</li>
<li>这个时候从管道的另一端被写入了2KB的数据</li>
<li>调用epoll_wait(2)，并且它会返回RFD，说明它已经准备好读取操作</li>
<li>然后我们读取了1KB的数据</li>
<li>调用epoll_wait(2)（两种工作模式有不同的差别）</li>
</ol>
<p><strong>LT模式：</strong><br>如果是LT模式，那么在第5步调用epoll_wait(2)之后，仍然能受到通知。</p>
<p><strong>ET模式：</strong><br>只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候，调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。</p>
<h3 id="epoll的实现原理"><a href="#epoll的实现原理" class="headerlink" title="epoll的实现原理"></a>epoll的实现原理</h3><p>在linux，一切皆文件．所以当调用<code>epoll_create</code>时，内核给这个<code>epoll</code>分配一个文件描述符，但是这个不是普通的文件，而是只服务于epoll。</p>
<p>当内核初始化epoll时，会开辟一块内核高速缓冲区，用于放置我们监听的对端socket，这些socket会以<strong>红黑树节点的形式</strong>保存在内核的<code>cache</code>里，以支持快速的查找，插入，删除。</p>
<p>同时，建立了一个<strong>list链表，用于存储准备就绪的事件</strong>．所以调用<code>epoll_wait</code>时，在timeout时间内，只是简单的观察这个list链表是否有数据，如果没有，则睡眠至超时时间到返回；如果有数据，则在超时时间到，拷贝至用户态<code>events</code>数组中．</p>
<p><em>那么，这个准备就绪list链表是怎么维护的呢？</em><br>当我们执行<code>epoll_ctl()</code>时，除了把socket_fd放到epoll系统里对应的红黑树上之外，还会<strong>给内核中断处理程序注册一个回调函数</strong>，告诉内核，<strong>如果这个句柄的中断到了，就把它放到准备就绪list链表里</strong>。所以，当一个socket上有数据到了，内核在把网卡上的数据copy到内核中后就来把socket插入到准备就绪链表里了。</p>
<p>epoll主要由两个结构体：eventpoll与epitem。epitem是每一个IO所对应的的事件。比如 <code>epoll_ctl()</code>的<code>EPOLL_CTL_ADD</code>操作的时候，就需要创建一个<code>epitem</code>。<code>eventpoll</code>是每一个epoll所对应的。比如<code>epoll_create</code>就是创建一个<code>eventpoll</code>。如下图所示，<code>eventpoll</code> 包含了 <code>lock</code>、<code>mtx</code>、<code>wq（等待队列）</code>与 <code>rdlist</code> 等成员，其中 <code>rdlist</code> 和 <code>rbr</code> 是我们所关心的。</p>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/11/20/Unix%E4%B8%AD%E7%9A%84%E4%BA%94%E7%A7%8D%20IO%20%E6%A8%A1%E5%9E%8B/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="SongyangJi">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="JsyBlog">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | JsyBlog">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2021/11/20/Unix%E4%B8%AD%E7%9A%84%E4%BA%94%E7%A7%8D%20IO%20%E6%A8%A1%E5%9E%8B/" class="post-title-link" itemprop="url">Unix/Linux 中的五种 IO 模型</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2021-11-20 20:20:00" itemprop="dateCreated datePublished" datetime="2021-11-20T20:20:00+08:00">2021-11-20</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">更新于</span>
      <time title="修改时间：2022-03-17 03:43:22" itemprop="dateModified" datetime="2022-03-17T03:43:22+08:00">2022-03-17</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/IO/" itemprop="url" rel="index"><span itemprop="name">IO</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>IO模型的选择在Linux网络编程中十分重要，在Unix&#x2F;Linux环境中主要提供了五种不同的IO模型，分别是</p>
<ol>
<li>阻塞式IO（blocking IO）；</li>
<li>非阻塞式IO（nonblocking IO）；</li>
<li>IO多路复用（ IO multiplexing）；</li>
<li>信号驱动式IO（signal driven IO）；</li>
<li>异步IO（asynchronous IO）。</li>
</ol>
<p>通常一个输入操作包含两个不同阶段：</p>
<ol>
<li>等待数据准备好</li>
<li>从内核向进程复制数据</li>
</ol>
<img src="blockread.png" style="zoom:40%;" />



<p>对于网络IO而言，对于一个网络套接字上的输入操作，第一步通常涉及到发生系统调用，用户态切换到内核态并等待数据从网络中到达，当所有等待分组到达时，数据被复制到内核中的某个缓冲区。第二步则是将数据从内核缓冲区复制到应用进程缓冲区。</p>
<p>磁盘文件的IO比较特殊，内核采用缓冲区cache加速磁盘IO请求。因而<strong>一旦请求的数据到达内核缓冲区cache，对磁盘的write()操作立即返回</strong>，而不用等待将数据写入磁盘后再返回（除非在打开文件时指定了O_SYNC标志）。与之相对应的read()操作将数据从内核缓冲区cache移动到用户的缓冲区中，如果请求的数据不在内核缓冲区cache中，<strong>内核会让进程休眠</strong>，同时执行对磁盘的读操作。</p>
<p>所以实际上在磁盘IO中，等待阶段是不存在的，因为磁盘文件并不像网络IO那样，需要等待远程传输数据。</p>
<h1 id="阻塞式I-x2F-O"><a href="#阻塞式I-x2F-O" class="headerlink" title="阻塞式I&#x2F;O"></a>阻塞式I&#x2F;O</h1><p>Linux中，默认情况下所有的socket都是阻塞的。这里有必要辨析以下阻塞和非阻塞这两个概念，这两个概念<strong>描述的是用户线程调用内核I&#x2F;O操作的方式</strong>，<strong>其中阻塞是指I&#x2F;O操作需要彻底完成后才返回到用户空间；而非阻塞则是指I&#x2F;O操作被调用后立即返回给用户一个状态值，不需要等到I&#x2F;O操作彻底完成</strong>。</p>
<p>除非特别指定，几乎所有的I&#x2F;O接口都是阻塞型的，即系统调用时不返回调用结果，只有当该系统调用获得结果或者超时出错才返回。这样的机制给网络编程带来了较大的影响，当线程因处理数据而处于阻塞状态时，线程将无法执行任何运算或者相应任何网络请求。</p>
<p><img src="/2021/11/20/Unix%E4%B8%AD%E7%9A%84%E4%BA%94%E7%A7%8D%20IO%20%E6%A8%A1%E5%9E%8B/blockIO.png"></p>
<p>在服务器端使用阻塞I&#x2F;O模型时结合<strong>多进程&#x2F;多线程</strong>技术。</p>
<p>让每一个连接都拥有独立的进程&#x2F;线程，任何一个连接的阻塞都不会影响到其他连接。（选择多进程还是多线程并无统一标准，因为进程的开销大于线程，所以在连接数较大的情况下推荐使用多线程。而进程相较于线程具有更高的安全性，所以如果单个服务执行体需要消耗较多的CPU资源，如需要进行大规模或长时间的数据运算或文件访问推荐使用多进程）。</p>
<p>当连接数规模继续增大，无论使用多线程还是多进程都会严重占据系统资源，降低系统对外界的响应效率，线程或者进程本身也更容易陷入假死。</p>
<p>此时可以采用“线程池”或“连接池”来降低创建和销毁进程&#x2F;线程的频率，减少系统开销。</p>
<p>总之这里的线程模型实际上是<strong>一个socket对应一个进程&#x2F;线程。</strong></p>
<h1 id="非阻塞式IO"><a href="#非阻塞式IO" class="headerlink" title="非阻塞式IO"></a>非阻塞式IO</h1><p>进程把一个套接字设置成非阻塞是在通知内核：<strong>当请求的I&#x2F;O操作非得把本进程投入睡眠才能完成时，不要把本进程投入睡眠，而是返回一个错误</strong>。</p>
<p> 因此如果在打开文件时设定了O_NONBLOCK标志，则会以非阻塞方式打开文件。</p>
<p>如果I&#x2F;O系统调用不能立即完成，则会返回错误而不是阻塞进程。非阻塞式I&#x2F;O可以<strong>实现周期性检查</strong>（轮询）某个文件描述符是否可执行I&#x2F;O操作。比如，设定一个输入文件描述符为非阻塞式的，然后周期性的执行非阻塞式读操作。如果需要同时检测多个文件描述符，则将其都设为非阻塞，然后一次轮询。但是这种<strong>轮询的效率不高</strong>，在轮询频率不高的情况下，程序响应I&#x2F;O事件的延迟将难以接受。换句话说，在一个紧凑的循环中做轮询就是在<strong>浪费CPU时间</strong>，因为多数时间调用会立即出错并返回。</p>
<p><img src="/2021/11/20/Unix%E4%B8%AD%E7%9A%84%E4%BA%94%E7%A7%8D%20IO%20%E6%A8%A1%E5%9E%8B/nonBlockIO.png"></p>
<blockquote>
<p>对于不能满足非阻塞式I&#x2F;O操作，System V会返回EAGAIN错误而源于Berkeley的4.3BSD返回EWOULDBLOCK。如今大多数系统都把这两个错误码定义为相同的值。（可查看<code>&lt;sys/errno.h&gt;</code>）</p>
</blockquote>
<h1 id="I-x2F-O多路复用"><a href="#I-x2F-O多路复用" class="headerlink" title="I&#x2F;O多路复用"></a>I&#x2F;O多路复用</h1><p><img src="/2021/11/20/Unix%E4%B8%AD%E7%9A%84%E4%BA%94%E7%A7%8D%20IO%20%E6%A8%A1%E5%9E%8B/iomulti.png"></p>
<p>I&#x2F;O多路复用（也叫做事件驱动I&#x2F;O）通过系统调用<code>select()</code>、<code>poll</code>、或者<code>epoll()</code>实现<strong>进程同时检查多个文件描述符</strong>，以找出其中任何一个是否可执行I&#x2F;O操作。通过上图可以看出I&#x2F;O多路复用与阻塞I&#x2F;O模型差别并不大，事实上还要差一些，因为这里使用了两个系统调用而阻塞I&#x2F;O只是用了一个系统调用。</p>
<p>但是<strong>I&#x2F;O多路复用的优势是单个线程&#x2F;进程可以同时处理多个连接</strong>。因此如果处理的连接数不是特别多的情况下使用I&#x2F;O多路复用模型的web server不一定比使用多线程技术的阻塞I&#x2F;O模型好。</p>
<p><strong>事实上，这种IO多路复用方式实际上也正是各种高性能网络框架以及高性能的网络服务器使用的最多的模式。</strong>所以，这也是我们学习的重点。</p>
<h1 id="信号驱动式I-x2F-O"><a href="#信号驱动式I-x2F-O" class="headerlink" title="信号驱动式I&#x2F;O"></a>信号驱动式I&#x2F;O</h1><p><img src="/2021/11/20/Unix%E4%B8%AD%E7%9A%84%E4%BA%94%E7%A7%8D%20IO%20%E6%A8%A1%E5%9E%8B/sigio.png"></p>
<p>信号驱动I&#x2F;O中，当文件描述符上可执行I&#x2F;O操作时，进程请求内核为自己发送一个信号，之后进程可以执行其他任务直到I&#x2F;O就绪为止，此时内核会发送信号给进程。建立一个针对套接字的信号驱动式I&#x2F;O需要进程执行以下三个步骤：</p>
<ol>
<li>建立<code>SIGIO</code>信号处理函数</li>
<li>设置该套接字的属主，通常使用<code>fcntl</code>的<code>F_SETOWN</code>命令设置</li>
<li>开启该套接字的信号驱动式I&#x2F;O，通常通过使用<code>fcnt</code>的<code>F_SETFL</code>命令打开<code>O_ASYNC</code>标志完成</li>
</ol>
<p>使用信号驱动式I&#x2F;O模型的主要优点是<strong>在等待数据到达期间，进程不会被阻塞</strong>。</p>
<p><strong>信号驱动式I&#x2F;O的应用</strong></p>
<ul>
<li><p>对于UDP上的使用比较简单，<code>SIGIO</code>信号只有在数据报到达套接字或者套接字发生异步错误时产生。因此当捕获对于某个UDP套接字的<code>SIGIO</code>信号时，我们调用recvfrom或者读入到达的数据报或者获取发生的异步错误。</p>
</li>
<li><p><strong>信号驱动式I&#x2F;O对于TCP套接字几乎无用</strong>，主要原因是<code>SIGIO</code>信号产生会过于频繁，并且其出现并没有告知我们发生了什么事件。比如，当一个进程既读又写一个TCP套接字时，当有数据到达或者当前写出的数据得到确认时，<code>SIGIO</code>信号都会产生，而信号处理函数无法区分这两种情况。 <strong>应该只考虑对监听TCP套接字使用<code>SIGIO</code>，因为对于监听TCP套接字产生<code>SIGIO</code>的唯一条件是某个新连接的完成</strong>。</p>
</li>
</ul>
<h1 id="异步I-x2F-O"><a href="#异步I-x2F-O" class="headerlink" title="异步I&#x2F;O"></a>异步I&#x2F;O</h1><p><img src="/2021/11/20/Unix%E4%B8%AD%E7%9A%84%E4%BA%94%E7%A7%8D%20IO%20%E6%A8%A1%E5%9E%8B/aio.png"></p>
<p>对于I&#x2F;O操作主要有两种分别是异步I&#x2F;O和同步I&#x2F;O，对于同步I&#x2F;O会导致请求进程阻塞，直到I&#x2F;O操作完成，即必须等待I&#x2F;O操作完成以后控制权才返回给用户进程；而异步I&#x2F;O不会导致请求进程阻塞，即无需等待I&#x2F;O操作完成就将控制权返回给用户进程。</p>
<ul>
<li><p>异步I&#x2F;O模型的工作机制</p>
<p>告知内核启动某个操作，并让内核在整个操作（包括将数据从内核复制到进程缓冲区）完成后通知我们。主要方式是调用<code>aio_read</code>函数向内核传递描述符、缓冲区指针、缓冲区大小（与read相同的三个参数）和文件偏移，并告知内核当整个操作完成时如何通知用户进程。该系统调用立即返回，在等待I&#x2F;O完成期间进程不被阻塞。</p>
</li>
<li><p>与信号驱动式I&#x2F;O模型的区别<br>信号驱动式I&#x2F;O是由内核告诉我们<strong>何时可以启动一个I&#x2F;O操作</strong>，而异步I&#x2F;O模型则是由内核通知我们<strong>I&#x2F;O操作何时完成</strong>。</p>
</li>
</ul>
<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>5种IO模式的对比：</p>
<p><img src="/2021/11/20/Unix%E4%B8%AD%E7%9A%84%E4%BA%94%E7%A7%8D%20IO%20%E6%A8%A1%E5%9E%8B/compareIO.png"></p>
<p>前4中模型的区别主要在于第一阶段，因为它们的第二阶段都是一样的：在数据从内核复制到用户空间的缓冲区期间，进程阻塞于<code>read</code>调用。</p>
<p>相反异步模型在这两个阶段都要进行处理，在完成的时候才会通知调用者已经完成。</p>
<h2 id="阻塞与非阻塞"><a href="#阻塞与非阻塞" class="headerlink" title="阻塞与非阻塞"></a>阻塞与非阻塞</h2><p>阻塞与非阻塞的区分是在第一阶段，也就是等待数据报有没有到的情况。</p>
<h2 id="同步与异步"><a href="#同步与异步" class="headerlink" title="同步与异步"></a>同步与异步</h2><ul>
<li><p>同步I&#x2F;O会导致请求进程阻塞，直到I&#x2F;O操作完成，即必须等待I&#x2F;O操作完成以后控制权才返回给用户进程；</p>
</li>
<li><p>异步I&#x2F;O不会导致请求进程阻塞，即无需等待I&#x2F;O操作完成就将控制权返回给用户进程。</p>
</li>
</ul>
<h2 id="非阻塞与异步的区别"><a href="#非阻塞与异步的区别" class="headerlink" title="非阻塞与异步的区别"></a>非阻塞与异步的区别</h2><p>在non-blocking IO中，虽然进程大部分时间都不会被block，但是它仍然要求进程去主动的check，并且当数据准备完成以后，也需要进程主动的再次调用recvfrom来将数据拷贝到用户内存。而asynchronous IO则完全不同。它就像是用户进程将整个IO操作交给了他人（kernel）完成，然后他人做完后发信号通知。在此期间，用户进程不需要去检查IO操作的状态，也不需要主动的去拷贝数据。</p>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/11/18/UDP/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="SongyangJi">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="JsyBlog">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | JsyBlog">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2021/11/18/UDP/" class="post-title-link" itemprop="url">UDP-用户数据报协议</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2021-11-18 00:19:44" itemprop="dateCreated datePublished" datetime="2021-11-18T00:19:44+08:00">2021-11-18</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">更新于</span>
      <time title="修改时间：2021-12-04 18:19:11" itemprop="dateModified" datetime="2021-12-04T18:19:11+08:00">2021-12-04</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/" itemprop="url" rel="index"><span itemprop="name">计算机网络</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h1 id="TCP-和-UDP-区别"><a href="#TCP-和-UDP-区别" class="headerlink" title="TCP 和 UDP 区别"></a>TCP 和 UDP 区别</h1><ol>
<li>连接</li>
</ol>
<p>TCP 是⾯向连接的传输层协议，传输数据前先要建⽴连接。</p>
<p>UDP 是不需要连接，即刻传输数据。</p>
<ol start="2">
<li><p>服务对象<br>TCP 是⼀对⼀的两点服务，即⼀条连接只有两个端点。<br>UDP ⽀持⼀对⼀、⼀对多、多对多的交互通信</p>
</li>
<li><p>可靠性<br>TCP 是可靠交付数据的，数据可以⽆差错、不丢失、不重复、按需到达。<br>UDP 是尽最⼤努⼒交付，不保证可靠交付数据。</p>
</li>
<li><p>拥塞控制、流量控制</p>
<p>TCP 有拥塞控制和流量控制机制，保证数据传输的安全性。UDP 则没有，即使⽹络⾮常拥堵了，也不会影响 UDP 的发送速率。</p>
</li>
<li><p>⾸部开销</p>
<p>TCP ⾸部⻓度较⻓，会有⼀定的开销，⾸部在没有使⽤「选项」字段时是 20 个字节，如果使⽤了「选项」</p>
</li>
</ol>
<p>字段则会变⻓的。</p>
<p>UDP ⾸部只有 8 个字节，并且是固定不变的，开销较⼩。</p>
<ol start="6">
<li><p>传输⽅式</p>
<p>TCP 是流式传输，没有边界，但保证顺序和可靠。</p>
<p>UDP 是⼀个包⼀个包的发送，是有边界的，但可能会丢包和乱序。</p>
</li>
<li><p>分⽚不同</p>
<p><strong>TCP 的数据⼤⼩如果⼤于 MSS ⼤⼩，则会在传输层进⾏分⽚</strong>，<strong>⽬标主机收到后，也同样在传输层组装 TCP数据包</strong>，如果中途丢失了⼀个分⽚，只需要传输丢失的这个分⽚。</p>
<p><strong>UDP 的数据⼤⼩如果⼤于 MTU ⼤⼩，则会在 IP 层进⾏分⽚</strong>，<strong>⽬标主机收到后，在 IP 层组装完数据，接着</strong></p>
<p><strong>再传给传输层</strong>，但是如果中途丢了⼀个分⽚，在实现可靠传输的 UDP 时则就需要重传所有的数据包，这样</p>
<p>传输效率⾮常差，所以通常 UDP 的报⽂应该⼩于 MTU。</p>
</li>
</ol>
<h1 id="TCP-和-UDP-应⽤场景"><a href="#TCP-和-UDP-应⽤场景" class="headerlink" title="TCP 和 UDP 应⽤场景"></a>TCP 和 UDP 应⽤场景</h1><p>由于 TCP 是⾯向连接，能保证数据的可靠性交付，因此经常⽤于：</p>
<ol>
<li>FTP ⽂件传输</li>
<li>HTTP &#x2F; HTTPS</li>
</ol>
<p>由于 UDP ⾯向⽆连接，它可以随时发送数据，再加上UDP本身的处理既简单⼜⾼效，因此经常⽤于：</p>
<ol>
<li>包总量较少的通信，如 DNS 、 SNMP 等</li>
<li>视频、⾳频等多媒体通信</li>
<li>⼴播通信</li>
</ol>
<h3 id="UDP-为什么是不可靠的？bind-和-connect-对于-UDP-的作用是什么"><a href="#UDP-为什么是不可靠的？bind-和-connect-对于-UDP-的作用是什么" class="headerlink" title="UDP 为什么是不可靠的？bind 和 connect 对于 UDP 的作用是什么"></a>UDP 为什么是不可靠的？bind 和 connect 对于 UDP 的作用是什么</h3><p>UDP 只有一个 socket 接收缓冲区，没有 socket 发送缓冲区，即只要有数据就发，不管对方是否可以正确接收。而在对方的 socket 接收缓冲区满了之后，新来的数据报无法进入到 socket 接受缓冲区，此数据报就会被丢弃，因此 UDP 不能保证数据能够到达目的地，此外，UDP 也没有流量控制和重传机制，故UDP的数据传输是不可靠的。</p>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/11/16/TCP/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="SongyangJi">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="JsyBlog">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | JsyBlog">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2021/11/16/TCP/" class="post-title-link" itemprop="url">TCP-传输控制协议</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2021-11-16 11:57:46" itemprop="dateCreated datePublished" datetime="2021-11-16T11:57:46+08:00">2021-11-16</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">更新于</span>
      <time title="修改时间：2022-09-28 13:35:40" itemprop="dateModified" datetime="2022-09-28T13:35:40+08:00">2022-09-28</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/" itemprop="url" rel="index"><span itemprop="name">计算机网络</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h1 id="TCP简介"><a href="#TCP简介" class="headerlink" title="TCP简介"></a>TCP简介</h1><h3 id="TCP是什么"><a href="#TCP是什么" class="headerlink" title="TCP是什么"></a>TCP是什么</h3><p><strong>传输控制协议</strong>（英语：<strong>T</strong>ransmission <strong>C</strong>ontrol <strong>P</strong>rotocol，缩写：<strong>TCP</strong>）是一种<strong>面向连接</strong>的、<strong>可靠</strong>的、<strong>基于字节流</strong>的<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E4%BC%A0%E8%BE%93%E5%B1%82">传输层</a>通信协议，由IETF的<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/RFC">RFC</a> <a target="_blank" rel="noopener" href="https://tools.ietf.org/html/rfc793">793</a>定义。</p>
<ul>
<li>⾯向连接：⼀定是⼀对⼀才能连接，不能像 UDP 协议可以⼀个主机同时向多个主机发送消息，也就是⼀</li>
</ul>
<p>对多是⽆法做到的；</p>
<ul>
<li><p>可靠的：⽆论的⽹络链路中出现了怎样的链路变化，TCP 都可以保证⼀个报⽂⼀定能够到达接收端；</p>
</li>
<li><p>字节流：消息是没有边界的，所以⽆论我们消息有多⼤都可以进⾏传输。并且消息是有序的，当</p>
</li>
</ul>
<p>前⼀个消息没有收到的时候，即使它先收到了后⾯的字节，那么也不能扔给应⽤层去处理，同时对重</p>
<p>复的报⽂会⾃动丢弃。</p>
<h3 id="TCP的功能与角色"><a href="#TCP的功能与角色" class="headerlink" title="TCP的功能与角色"></a>TCP的功能与角色</h3><p>在因特网协议族（Internet protocol suite）中，TCP层是位于<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E7%BD%91%E9%99%85%E5%8D%8F%E8%AE%AE">IP</a>层之上，<em>应用层</em>之下的中间层。不同主机的应用层之间经常需要可靠的、像<em>管道</em>一样的连接，但是IP层不提供这样的流机制，而是提供不可靠的包交换。</p>
<p>在简化的计算机网络<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/OSI%E6%A8%A1%E5%9E%8B">OSI模型</a>中，它完成第四层传输层所指定的功能。<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E7%94%A8%E6%88%B7%E6%95%B0%E6%8D%AE%E6%8A%A5%E5%8D%8F%E8%AE%AE">用户数据报协议</a>（UDP）是同一层内另一个重要的传输协议。</p>
<p>应用层向TCP层发送用于网间传输的、字节表示的数据流。</p>
<p>然后<strong>TCP把数据流分割成适当长度的报文段</strong>（通常受该计算机连接的网络的数据链路层的<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E6%9C%80%E5%A4%A7%E4%BC%A0%E8%BE%93%E5%8D%95%E5%85%83">最大传输单元</a>（MTU）的限制）。</p>
<p>之后TCP把结果包传给IP层，由它来透过网络将包传送给接收端实体的TCP层。</p>
<h3 id="TCP-是如何保证可靠性的"><a href="#TCP-是如何保证可靠性的" class="headerlink" title="TCP 是如何保证可靠性的"></a>TCP 是如何保证可靠性的</h3><ul>
<li><strong>数据分块</strong>：应用数据被分割成 TCP 认为最适合发送的数据块。</li>
<li><strong>序列号和确认应答</strong>：TCP 给发送的每一个包进行编号，在传输的过程中，每次接收方收到数据后，都会对传输方进行确认应答，即发送 ACK 报文，这个 ACK 报文当中带有对应的确认序列号，告诉发送方成功接收了哪些数据以及下一次的数据从哪里开始发。除此之外，接收方可以根据序列号对数据包进行排序，把有序数据传送给应用层，并丢弃重复的数据。</li>
<li><strong>校验和</strong>： TCP 将保持它首部和数据部分的检验和。这是一个端到端的检验和，目的是检测数据在传输过程中的任何变化。如果收到报文段的检验和有差错，TCP 将丢弃这个报文段并且不确认收到此报文段。</li>
<li><strong>流量控制</strong>： TCP 连接的双方都有一个固定大小的缓冲空间，发送方发送的数据量不能超过接收端缓冲区的大小。当接收方来不及处理发送方的数据，会提示发送方降低发送的速率，防止产生丢包。TCP 通过滑动窗口协议来支持流量控制机制。</li>
<li><strong>拥塞控制</strong>： 当网络某个节点发生拥塞时，减少数据的发送。</li>
<li><strong>重传机制</strong>： 超时重传：当 TCP 发出一个报文段后，它启动一个定时器，等待目的端确认收到这个报文段，<strong>如果超过某个时间还没有收到确认，将重发这个报文段</strong>；快速重传：快速重传的⼯作⽅式是<strong>当收到三个相同的 ACK 报⽂时，会在定时器过期之前，重传丢失的报⽂段</strong>。</li>
</ul>
<p>下面的内容和其他文章会详细叙述这一点。</p>
<h2 id="什么是-TCP-连接"><a href="#什么是-TCP-连接" class="headerlink" title="什么是 TCP 连接"></a>什么是 TCP 连接</h2><p>⽤于保证可靠性和流量控制维护的某些状态信息，这些信息的组合，包括<strong>Socket</strong>、序列号和窗⼝</p>
<p>⼤⼩称为连接。</p>
<ol>
<li>Socket：由 IP 地址和端⼝号组成;</li>
<li>序列号：⽤来解决乱序问题等;</li>
<li>窗⼝⼤⼩：⽤来做流量控制</li>
</ol>
<p>TCP 四元组可以唯⼀的确定⼀个连接，四元组包括如下：</p>
<ul>
<li>源地址</li>
<li>源端⼝</li>
<li>⽬的地址</li>
<li>⽬的端⼝</li>
</ul>
<blockquote>
<p>一个套接字由相关五元组构成，协议、本地地址、本地端口、远程地址、远程端口。</p>
</blockquote>
<h2 id="TCP的可靠性简单介绍"><a href="#TCP的可靠性简单介绍" class="headerlink" title="TCP的可靠性简单介绍"></a>TCP的可靠性简单介绍</h2><p>TCP为了保证不发生丢包，就给每个包一个序号，同时序号也保证了传送到接收端实体的包的按序接收（<strong>序号的作用: 1.包不丢失  2.包不重复 3. 有序接受</strong>）。</p>
<p>然后接收端实体对已成功收到的包发回一个相应的确认信息（ACK）；如果发送端实体在合理的<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E4%BE%86%E5%9B%9E%E9%80%9A%E8%A8%8A%E5%BB%B6%E9%81%B2">往返时延</a>（RTT）内未收到确认，那么对应的数据包就被假设为已丢失并进行重传。</p>
<p>TCP用一个<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E6%A0%A1%E9%AA%8C%E5%92%8C">校验和</a>函数来检验数据是否有错误，在发送和接收时都要计算校验和。</p>
<blockquote>
<p>数据在TCP层称为流（Stream），数据分组称为分段（Segment）。作为比较，数据在IP层称为Datagram，数据分组称为分片（Fragment）。 UDP 中分组称为Message。 </p>
</blockquote>
<h2 id="TCP-连接数有多少"><a href="#TCP-连接数有多少" class="headerlink" title="TCP 连接数有多少"></a>TCP 连接数有多少</h2><blockquote>
<p>有⼀个 IP 的服务器监听了⼀个端⼝，它的 TCP 的最⼤连接数是多少？</p>
</blockquote>
<p><strong>理论最大值</strong></p>
<ul>
<li><p>Client 最大 TCP 连接数<br>client 在每次发起 TCP 连接请求时，<strong>如果自己并不指定端口的话，系统会随机选择一个本地端口</strong>（local port），<strong>该端口是独占的</strong>，不能和其他 TCP 连接共享。TCP 端口的数据类型是 unsigned short，因此本地端口个数最大只有 65536，除了端口 0 不能使用外，其他端口在空闲时都可以正常使用，这样可用端口最多有 65535 （2的16次方）个。</p>
</li>
<li><p>Server最大 TCP 连接数<br>server 通常固定在某个本地端口上监听，等待 client 的连接请求。不考虑地址重用（Unix 的 SO_REUSEADDR 选项）的情况下，<strong>即使 server 端有多个 IP，本地监听端口也是独占的</strong>，因此 server 端 TCP 连接 4 元组中只有客户端的 IP 地址和端口号是可变的，因此<strong>最大 TCP 连接&#x3D;客户端 IP 数 × 客户端 port 数</strong>，对 IPV4，在不考虑 IP 地址分类的情况下，最大 TCP 连接数约为 2 的 32 次方（IP 数）× 2 的 16 次方（port 数），也就是 server 端单机最大 TCP 连接数约为 2 的 48 次方。</p>
</li>
</ul>
<ol>
<li>主要是⽂件描述符限制，socket 都是作为⽂件来处理，所以⾸先要通过 ulimit 配置⽂件描述符的数⽬；</li>
<li>内存限制，每个 TCP 连接都要占⽤⼀定内存，操作系统的内存是有限的；</li>
<li>IP协议的规定（一些 IP 地址和端口具有特殊含义，没有对外开放）。</li>
</ol>
<p>对 server 端，通过增加内存、修改最大文件描述符个数等参数，<strong>单机最大并发 TCP 连接数超过 10 万</strong> 是没问题的。</p>
<h1 id="TCP段的头"><a href="#TCP段的头" class="headerlink" title="TCP段的头"></a>TCP段的头</h1><p>大小：<strong>固定部分为20字节，选项最多40字节，限制60字节</strong>。</p>
<p><img src="/2021/11/16/TCP/tcp-header.png"></p>
<ul>
<li><p>来源连接端口（16位长）－发送端连接端口；</p>
</li>
<li><p>目的连接端口（16位长）－接收端连接端口；</p>
</li>
<li><p>序列号（seq，32位长）</p>
<ul>
<li>如果含有同步标志位（SYN），则此为最初的用于同步的序列号；第一个数据比特的序列码为本序列号+1；</li>
<li>如果没有同步标志位（SYN），则此为第一个数据比特的序列码。</li>
</ul>
</li>
<li><p>确认号（ack，32位长）— <strong>下一次期望收到的数据的开始序列号</strong>，也即已经收到的数据的字节长度加1。</p>
</li>
<li><p>数据偏移（4位长）— 以4字节为单位计算出的数据段开始地址的偏移值（因为选项部分长度是可变的），也就是<strong>TCP头部长度</strong>。</p>
</li>
<li><p>保留（3比特长）— 须置0。</p>
</li>
<li><p>标志符（9比特长）</p>
<ul>
<li>NS — ECN-nonce。ECN显式拥塞通知（Explicit Congestion Notification）是对TCP的扩展，定义于 RFC 3540 （2003）。ECN允许拥塞控制的端对端通知而避免丢包。ECN为一项可选功能，如果底层网络设施支持，则可能被启用ECN的两个端点使用。在ECN成功协商的情况下，ECN感知路由器可以在IP头中设置一个标记来代替丢弃数据包，以标明阻塞即将发生。数据包的接收端回应发送端的表示，降低其传输速率，就如同在往常中检测到包丢失那样。</li>
<li>CWR — Congestion Window Reduced，定义于 RFC 3168（2001）。</li>
<li>ECE — ECN-Echo有两种意思，取决于SYN标志的值，定义于 RFC 3168（2001）。</li>
<li>URG — 为1表示高优先级数据包，紧急指针字段有效。</li>
<li><strong>ACK</strong> — 为1表示<strong>确认号（ack）</strong>字段有效，用于表示已经接收到消息了</li>
<li>PSH — 为1表示是带有PUSH标志的数据，指示接收方应该尽快将这个报文段交给应用层而不用等待缓冲区装满。</li>
<li><strong>RST</strong> — 为1<strong>表示出现严重差错</strong>，可能需要重新创建TCP连接，还可以<strong>用于拒绝非法的报文段和拒绝连接请求</strong>。</li>
<li><strong>SYN</strong> — 为1表示这是<strong>连接请求或是连接接受请求</strong>，<strong>用于创建连接和使顺序号同步</strong>。</li>
<li><strong>FIN</strong> — 为1表示<strong>发送方没有数据要传输</strong>了，要求释放连接。</li>
</ul>
</li>
<li><p><strong>窗口</strong>（WIN，16位长）— 表示从确认号开始，本报文的<strong>发送方可以接收的字节数</strong>，即接收窗口大小。用于流量控制。(2^16 &#x3D; 64K，这个数值并不大，所以头部选项里有窗口扩大因子这个选项)。</p>
</li>
<li><p>校验和（Checksum，16位长）—对整个的TCP报文段<strong>，包括TCP头部和TCP数据</strong>，以16位字进行计算所得。这是一个强制性的字段。</p>
</li>
<li><p>紧急指针（16位长）—本报文段中的紧急数据的最后一个字节的序号。</p>
</li>
<li><p>选项字段 — 最多40字节（必须是4字节的倍数）。每个选项的开始是1字节的kind字段，说明选项的类型，</p>
<p>每个选项具有<strong>类型-长度-值</strong>的结构。</p>
<ul>
<li><p>0：选项表结束（1字节）</p>
</li>
<li><p>1：无操作（1字节）用于选项字段之间的字边界对齐。</p>
</li>
<li><p>2：<strong>最大报文段长度</strong>（4字节，<em>Maximum Segment Size</em>，<strong>MSS</strong>）通常在创建连接而设置SYN标志的数据包中指明这个选项，指明本端所能接收的最大长度的报文段。通常将MSS设置为（<strong>MTU - 40</strong>）字节，携带TCP报文段的IP数据报的长度就不会超过MTU（MTU最大长度为1518字节，最短为64字节），<strong>从而避免本机发生IP分片</strong>。<strong>只能出现在同步报文段中</strong>，否则将被忽略。</p>
</li>
<li><p>3：<strong>窗口扩大因子</strong>（3字节，wscale），取值0-14。用来把TCP的窗口的值左移的位数，使窗口值乘倍。<strong>只能出现在同步报文段中</strong>，否则将被忽略。这是因为现在的TCP接收数据缓冲区（接收窗口）的长度通常大于65535字节。</p>
</li>
<li><p>4：sackOK—发送端支持并同意使用SACK选项。</p>
</li>
<li><p>5：SACK实际工作的选项。</p>
</li>
<li><p>8：<strong>时间戳</strong>（10字节，TCP Timestamps Option，TSopt）。一旦在连接建立阶段启用了它，那么每个数据包都要包含这个选项，<strong>主要用来计算来回时间样值</strong>，该值被用来估算多长时间之后数据包可以被认为丢失。</p>
<ul>
<li>发送端的时间戳（Timestamp Value field，TSval，4字节）</li>
<li>时间戳回显应答（Timestamp Echo Reply field，TSecr，4字节）</li>
</ul>
</li>
<li><p>19：MD5摘要，将TCP伪首部、校验和为0的TCP首部、TCP数据段、通信双方约定的密钥（可选）计算出<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/MD5">MD5</a>摘要值并附加到该选项中，作为类似对TCP报文的签名。通过 <a target="_blank" rel="noopener" href="https://tools.ietf.org/html/rfc2385">RFC 2385</a> 引入，主要用于增强<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E8%BE%B9%E7%95%8C%E7%BD%91%E5%85%B3%E5%8D%8F%E8%AE%AE">BGP</a>通信的安全性。</p>
</li>
<li><p>29：安全摘要，通过 <a target="_blank" rel="noopener" href="https://tools.ietf.org/html/rfc5925">RFC 5925</a> 引入，将“MD5摘要”的散列方法更换为<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/SHA%E5%AE%B6%E6%97%8F">SHA散列算法</a>。</p>
</li>
</ul>
</li>
</ul>
<h1 id="TCP-连接管理有限状态自动机"><a href="#TCP-连接管理有限状态自动机" class="headerlink" title="TCP 连接管理有限状态自动机"></a>TCP 连接管理有限状态自动机</h1><h2 id="有限状态机"><a href="#有限状态机" class="headerlink" title="有限状态机"></a>有限状态机</h2><p>有限状态机 (FSM : Finite State Machine) 又称有限状态自动机，简称状态机，是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。FSM 的四个基本概念如下：</p>
<ol>
<li>状态 state: 描述机器在特定时间上处于的 环境(circumstance)或状况(status)</li>
<li>转换 Transition: 从一种状态到另一种状态的行为(act)</li>
<li>事件 Event: 导致状态发生变化的事情</li>
<li>动作 Action: 机器从一种状态转换之前对事件所做的响应</li>
</ol>
<p>FSM通过解释协议可以处于的所有不同状态、可以在每个状态中发生的事件、针对事件采取的操作以及结果发生的转换来描述协议。协议通常在第一次运行时以特定的开始状态启动。尔后，它遵循一系列步骤使其进入常规操作状态，并根据特定类型的输入或其他情况移动到其他状态。状态机之所以称为有限状态机，是因为只有有限数量的状态。</p>
<p>建立连接和释放连接所需要的步骤，可以用一个有限状态自动机来表示，该状态自动机的11种状态如下。</p>
<p>在每一种状态中，都存在特定的合法事件：当一个合法事件发生时，可能需要采取某个动作；当发生其他事件时，则报告一个错误。</p>
<p>下表为TCP状态码列表，以<strong>S</strong>指代服务器，<strong>C</strong>指代客户端，<strong>S&amp;C</strong>表示两者，<strong>S&#x2F;C</strong>表示两者之一：</p>
<ul>
<li><p>LISTEN S</p>
<p>服务器等待从任意远程TCP端口的连接请求。侦听状态。</p>
</li>
<li><p>SYN-SENT C</p>
<p>客户在发送连接请求后等待匹配的连接请求。通过connect()函数向服务器发出一个同步（SYNC）信号后进入此状态。</p>
</li>
<li><p>SYN-RECEIVED S</p>
<p>服务器已经收到并发送同步（SYNC）信号之后等待确认（ACK）请求。</p>
</li>
<li><p>ESTABLISHED S&amp;C</p>
<p>服务器与客户的连接已经打开，收到的数据可以发送给用户。数据传输步骤的正常情况。此时连接两端是平等的。这称作全连接。</p>
</li>
<li><p>FIN-WAIT-1 S&amp;C</p>
<p>（服务器或客户）主动关闭端调用close（）函数发出FIN请求包，表示<strong>本方的数据发送全部结束</strong>，等待TCP连接另一端的ACK确认包或FIN&amp;ACK请求包。</p>
</li>
<li><p>FIN-WAIT-2 S&amp;C</p>
<p>主动关闭端在FIN-WAIT-1状态下收到ACK确认包，进入等待远程TCP的连接终止请求的半关闭状态。这时<strong>主动关闭端可以接收数据，但不再发送数据</strong>。</p>
</li>
<li><p>CLOSE-WAIT S&amp;C</p>
<p>被动关闭端接到FIN后，就发出ACK以回应FIN请求，并进入等待本地用户的连接终止请求的半关闭状态。这时<strong>被动关闭端可以发送数据，但不再接收数据</strong>。</p>
</li>
<li><p>CLOSING S&amp;C</p>
<p>在发出FIN后，又收到对方发来的FIN后，进入等待对方对己方的连接终止（FIN）的确认（ACK）的状态。少见。</p>
</li>
<li><p>LAST-ACK S&amp;C</p>
<p>被动关闭端全部数据发送完成之后，<strong>向主动关闭端发送FIN，进入等待确认包</strong>的状态。</p>
</li>
<li><p>TIME-WAIT S&#x2F;C</p>
<p>主动关闭端接收到FIN后，就发送ACK包，<strong>等待足够时间以确保被动关闭端收到了终止请求的确认包</strong>。（按照RFC 793，一个连接可以在TIME-WAIT保证最大四分钟，即最大分段寿命（Maximum Segment Lifetime）的2倍）</p>
</li>
<li><p>CLOSED S&amp;C<br>完全没有连接。</p>
</li>
</ul>
<blockquote>
<p>CLOSED：无连接是活动的或正在进行<br>LISTEN：服务器在等待进入呼叫<br>SYN_RECV：一个连接请求已经到达，等待确认<br>SYN_SENT：应用已经开始，打开一个连接<br>ESTABLISHED：正常数据传输状态<br>FIN_WAIT1：应用说它已经完成<br>FIN_WAIT2：另一边已同意释放<br>ITMED_WAIT：等待所有分组死掉<br>CLOSING：两边同时尝试关闭<br>TIME_WAIT：另一边已初始化一个释放<br>LAST_ACK：等待所有分组死掉</p>
</blockquote>
<p><img src="/2021/11/16/TCP/tcpfsm.jpg" alt="img"></p>
<p>另附一张图：</p>
<img src="tcp-state.jpg" style="zoom:60%;" />









<h1 id="TCP三次握手"><a href="#TCP三次握手" class="headerlink" title="TCP三次握手"></a>TCP三次握手</h1><h2 id="三次握手的过程"><a href="#三次握手的过程" class="headerlink" title="三次握手的过程"></a>三次握手的过程</h2><p><strong>图示</strong></p>
<p><img src="/2021/11/16/TCP/3way.png"></p>
<p><strong>文字描述</strong>（可能更清楚）</p>
<ol>
<li>A –&gt; B  SYN my sequence number is X</li>
<li>A &lt;– B  ACK your sequence number is X</li>
<li>A &lt;– B  SYN my sequence number is Y</li>
<li>A –&gt; B  ACK your sequence number is Y</li>
</ol>
<p>2与3都是 B 发送给 A，因此可以合并在一起，因此称为<code>three way (or three message) handshake</code>。</p>
<p>三次握手是 TCP 连接的建立过程。在握手之前，主动打开连接的客户端结束 CLOSE 阶段，被动打开的服务器也结束 CLOSE 阶段，并进入 LISTEN 阶段。随后进入三次握手阶段：</p>
<p>① 首先客户端向服务器发送一个 SYN 包，并等待服务器确认，其中：</p>
<ul>
<li>标志位为 SYN，表示请求建立连接；</li>
<li>序号为 Seq &#x3D; x（x 一般取随机数）；</li>
<li>随后客户端进入 SYN-SENT 阶段。</li>
</ul>
<p>② 服务器接收到客户端发来的 SYN 包后，对该包进行确认后结束 LISTEN 阶段，并返回一段 TCP 报文，其中：</p>
<ul>
<li>标志位为 SYN 和 ACK，表示确认客户端的报文 Seq 序号有效，服务器能正常接收客户端发送的数据，并同意创建新连接；</li>
<li>序号为 Seq &#x3D; y；</li>
<li>确认号为 Ack &#x3D; x + 1，表示收到客户端的序号 Seq 并将其值加 1 作为自己确认号 Ack 的值。</li>
<li>随后服务器端进入 SYN-RECV 阶段。</li>
</ul>
<p>③ 客户端接收到发送的 SYN + ACK 包后，明确了从客户端到服务器的数据传输是正常的，从而结束 SYN-SENT 阶段。并返回最后一段报文。其中：</p>
<ul>
<li>标志位为 ACK，表示确认收到服务器端同意连接的信号；</li>
<li>序号为 Seq &#x3D; x + 1，表示收到服务器端的确认号 Ack，并将其值作为自己的序号值；</li>
<li>确认号为 Ack&#x3D; y + 1，表示收到服务器端序号 seq，并将其值加 1 作为自己的确认号 Ack 的值。</li>
<li>随后客户端进入 ESTABLISHED。当服务器端收到来自客户端确认收到服务器数据的报文后，得知从服务器到客户端的数据传输是正常的，从而结束 SYN-RECV 阶段，进入 ESTABLISHED 阶段，从而完成三次握手。</li>
</ul>
<h2 id="三次握手的意义"><a href="#三次握手的意义" class="headerlink" title="三次握手的意义"></a>三次握手的意义</h2><blockquote>
<p>常见问题：TCP 为什么是三次握手，而不是两次或四次？</p>
</blockquote>
<p>三次握手本身这个说法就是有点问题的。（有点抬杠）</p>
<p>其实三次握手这个说法不好，其实是双方各一次握手，各一次确认，其中一次握手和确认合并在一起，其实质就是双向握手+双向确认的过程。<br>这个解释也正是上面的文字描述的过程。</p>
<h3 id="教科书的回答"><a href="#教科书的回答" class="headerlink" title="教科书的回答"></a>教科书的回答</h3><p>在谢希仁著《计算机网络》第四版中讲“三次握手”的目的是<strong>为了防止已失效的连接请求报文段突然又传送到了服务端，因而产生错误</strong>。</p>
<p>在另一部经典的《计算机网络》一书中讲“三次握手”的目的是为了解决<strong>网络中存在延迟的重复分组</strong>的问题。</p>
<p>这两种不用的表述其实阐明的是同一个问题。</p>
<p>下面会对这两个问题作出详细解释。</p>
<h3 id="最简洁的回答"><a href="#最简洁的回答" class="headerlink" title="最简洁的回答"></a>最简洁的回答</h3><p>简单说，让双方都证实对方能发收。<br>知道对方能收是因为收到对方的因为收到而发的回应。</p>
<ol>
<li>A发，B收， B知道A能发</li>
<li>B发，A收， A知道B能发收</li>
<li>A发，B收， B知道A能收</li>
</ol>
<h3 id="综合全面的回答"><a href="#综合全面的回答" class="headerlink" title="综合全面的回答"></a>综合全面的回答</h3><ul>
<li>三次握手才可以阻止重复历史连接的初始化（主要原因）</li>
<li>三次握手才可以同步双方的初始序列号</li>
<li>三次握手才可以避免资源浪费</li>
</ul>
<h3 id="阻止重复历史连接的初始化"><a href="#阻止重复历史连接的初始化" class="headerlink" title="阻止重复历史连接的初始化"></a>阻止重复历史连接的初始化</h3><p>我们来看看 RFC 793 指出的 TCP 连接使用三次握手的<strong>首要原因</strong>：</p>
<p><em>The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.</em></p>
<p>简单来说，三次握手的<strong>首要原因是为了防止旧的重复连接初始化造成混乱。</strong></p>
<p>网络环境是错综复杂的，往往并不是如我们期望的一样，先发送的数据包，就先到达目标主机，相反可能会由于网络拥堵等原因，会使得旧的数据包，先到达目标主机，那么这种情况下 TCP 三次握手是如何避免的呢？</p>
<img src="avoid-old-duplicate-CR.jpg" style="zoom:50%;" />





<p>客户端连续发送多次 SYN 建立连接的报文，在网络拥堵等情况下：</p>
<ul>
<li>一个旧 SYN 报文比最新的 SYN  报文早到达了服务端；</li>
<li>那么此时服务端就会回一个 <code>SYN + ACK</code> 报文给客户端；</li>
<li>客户端收到后可以根据自身的上下文，判断这是一个历史连接（序列号过期或超时），那么客户端就会发送 <code>RST</code> 报文给服务端，表示中止这一次连接。</li>
</ul>
<p>如果是两次握手连接，就不能判断当前连接是否是历史连接，三次握手则可以在客户端（发送方）准备发送第三次报文时，客户端因有足够的上下文来判断当前连接是否是历史连接：</p>
<ul>
<li>如果是历史连接（序列号过期或超时），则第三次握手发送的报文是 <code>RST</code> 报文，以此中止历史连接；</li>
<li>如果不是历史连接，则第三次发送的报文是 <code>ACK</code> 报文，通信双方就会成功建立连接；</li>
</ul>
<p>所以， TCP 使用三次握手建立连接的最主要原因是<strong>防止历史连接初始化了连接。</strong></p>
<h3 id="同步双方的初始序列号"><a href="#同步双方的初始序列号" class="headerlink" title="同步双方的初始序列号"></a>同步双方的初始序列号</h3><p>TCP 协议的通信双方， 都必须维护一个「序列号」， 序列号是可靠传输的一个关键因素，它的作用：</p>
<ul>
<li>接收方可以去除重复的数据；</li>
<li>接收方可以根据数据包的序列号按序接收；</li>
<li>可以标识发送出去的数据包中， 哪些是已经被对方收到的；</li>
</ul>
<p>可见，序列号在 TCP 连接中占据着非常重要的作用，所以当客户端发送携带「初始序列号」的 <code>SYN</code> 报文的时候，需要服务端回一个 <code>ACK</code> 应答报文，表示客户端的 SYN 报文已被服务端成功接收，那当服务端发送「初始序列号」给客户端的时候，依然也要得到客户端的应答回应，<strong>这样一来一回，才能确保双方的初始序列号能被可靠的同步。</strong></p>
<img src="4and3way.jpg" style="zoom:50%;" />



<p>四次握手其实也能够可靠的同步双方的初始化序号，但由于<strong>第二步和第三步可以优化成一步</strong>，所以就成了「三次握手」。</p>
<p>而两次握手只保证了一方的初始序列号能被对方成功接收，没办法保证双方的初始序列号都能被确认接收。</p>
<h3 id="避免资源浪费"><a href="#避免资源浪费" class="headerlink" title="避免资源浪费"></a>避免资源浪费</h3><p>如果只有「两次握手」，当客户端的 <code>SYN</code> 请求连接在网络中阻塞，客户端没有接收到 <code>ACK</code> 报文，就会重新发送 <code>SYN</code> ，由于没有第三次握手，服务器不清楚客户端是否收到了自己发送的建立连接的 <code>ACK</code> 确认信号，所以每收到一个 <code>SYN</code> 就只能先主动建立一个连接，这会造成什么情况呢？</p>
<p>如果客户端的 <code>SYN</code> 阻塞了，重复发送多次 <code>SYN</code> 报文，那么服务器在收到请求后就会<strong>建立多个冗余的无效链接，造成不必要的资源浪费。</strong></p>
<img src="2-way.jpg" alt="img" style="zoom:50%;" />



<p>即两次握手会造成消息滞留情况下，服务器重复接受无用的连接请求 <code>SYN</code> 报文，而造成重复分配资源。</p>
<h3 id="回归本质的回答"><a href="#回归本质的回答" class="headerlink" title="回归本质的回答"></a>回归本质的回答</h3><p>这个问题的本质是, 信道不可靠, 每个包能到达跟下一个包能到达没有什么必然的联系。</p>
<p>但是通信双发需要就某个问题达成一致，而要解决这个问题,  无论你在消息中包含什么信息, 三次通信是理论上的最小值. 所以三次握手不是TCP本身的要求, 而是为了满足”在不可靠信道上可靠地传输信息”这一需求所导致的。</p>
<p>理论上讲不论握手多少次都不能确认一条信道是“可靠”的，但通过3次握手可以至少确认它是“可用”的，再往上加握手次数不过是提高“它是可用的”这个结论的可信程度。</p>
<p>这个问题在谢希仁版《计算机网络》里说了。</p>
<p><strong>三次是保证双方互相明确对方能收能发的最低值。</strong></p>
<p>因此，如果信道是可靠的, 即无论什么时候发出消息, 对方一定能收到，或者你不关心是否要保证对方收到你的消息, 那就能像UDP那样直接发送消息就可以了。</p>
<p>这可视为对“三次握手”目的的另一种解答思路。</p>
<h2 id="面试常见问题汇总"><a href="#面试常见问题汇总" class="headerlink" title="面试常见问题汇总"></a>面试常见问题汇总</h2><h3 id="三次握手的握手信息对方没有收到会怎么样"><a href="#三次握手的握手信息对方没有收到会怎么样" class="headerlink" title="三次握手的握手信息对方没有收到会怎么样"></a>三次握手的握手信息对方没有收到会怎么样</h3><h1 id="TCP四次挥手"><a href="#TCP四次挥手" class="headerlink" title="TCP四次挥手"></a>TCP四次挥手</h1><h2 id="四次挥手的过程"><a href="#四次挥手的过程" class="headerlink" title="四次挥手的过程"></a>四次挥手的过程</h2><p><strong>TCP连接是全双工的，但是在释放连接的时候最好把它看成是一对单双工连接。</strong></p>
<ol>
<li>客户端打算关闭连接，此时会发送⼀个 TCP ⾸部 FIN 标志位被置为 1 的报⽂，也即 FIN 报⽂，之后客</li>
</ol>
<p>户端进⼊ FIN_WAIT_1 状态。</p>
<ol start="2">
<li><p>服务端收到该报⽂后，就向客户端发送 ACK 应答报⽂，接着服务端进⼊ CLOSED_WAIT 状态。</p>
</li>
<li><p>客户端收到服务端的 ACK 应答报⽂后，之后进⼊ FIN_WAIT_2 状态。</p>
</li>
<li><p>等待服务端处理完数据后，也向客户端发送 FIN 报⽂，之后服务端进⼊ LAST_ACK 状态。</p>
</li>
<li><p>客户端收到服务端的 FIN 报⽂后，回⼀个 ACK 应答报⽂，之后进⼊ TIME_WAIT 状态</p>
</li>
<li><p>服务器收到了 ACK 应答报⽂后，就进⼊了 CLOSED 状态，⾄此服务端已经完成连接的关闭。</p>
</li>
</ol>
<p>客户端在经过 2MSL ⼀段时间后，⾃动进⼊ CLOSED 状态，⾄此客户端也完成连接的关闭。</p>
<p><strong>你可以看到，每个⽅向都需要⼀个 FIN 和⼀个 ACK，因此通常被称为四次挥⼿。</strong></p>
<p><strong>图示</strong></p>
<img src="4way.png" style="zoom:50%;" />





<p><strong>双⽅都可以主动断开连接</strong>，断开连接后主机中的<em>资源</em>将被释放。</p>
<p>这⾥⼀点需要注意是：主动关闭连接的，才有 <strong>TIME_WAIT</strong> 状态。</p>
<h2 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h2><h3 id="为什么挥⼿需要四次"><a href="#为什么挥⼿需要四次" class="headerlink" title="为什么挥⼿需要四次"></a>为什么挥⼿需要四次</h3><p>再来回顾下四次挥⼿双⽅发 FIN 包的过程，就能理解为什么需要四次了。</p>
<p>关闭连接时:</p>
<ol>
<li><p>客户端向服务端发送 FIN 时，仅仅表示客户端不再发送数据了但是还能接收数据。</p>
</li>
<li><p>服务器收到客户端的 FIN 报⽂时，先回⼀个 ACK 应答报⽂，⽽服务端可能还有数据需要处理和发送，等</p>
</li>
</ol>
<p>服务端不再发送数据时，才发送 FIN 报⽂给客户端来表示同意现在关闭连接。</p>
<p>从上⾯过程可知，<strong>服务端通常需要等待完成数据的发送和处理</strong>，所以<strong>服务端的 ACK 和 FIN ⼀般都会分开发</strong></p>
<p><strong>送</strong>，从⽽⽐三次握⼿导致多了⼀次。</p>
<blockquote>
<p>举个例子：A 和 B 打电话，通话即将结束后，A 说“我没啥要说的了”，B回答“我知道了”，但是 B 可能还会有要说的话，A 不能要求 B 跟着自己的节奏结束通话，于是 B 可能又巴拉巴拉说了一通，最后 B 说“我说完了”，A 回答“知道了”，这样通话才算结束。</p>
</blockquote>
<h3 id="为什么需要-TIME-WAIT-状态"><a href="#为什么需要-TIME-WAIT-状态" class="headerlink" title="为什么需要 TIME_WAIT 状态"></a>为什么需要 TIME_WAIT 状态</h3><p>主动发起关闭连接的⼀⽅，才会有 TIME-WAIT 状态。</p>
<p>需要 TIME-WAIT 状态，主要是两个原因：</p>
<ol>
<li><p>防⽌具有相同四元组的旧数据包被收到；</p>
</li>
<li><p>保证<em>被动关闭连接</em>的⼀⽅能被正确的关闭，即保证最后的 ACK 能让被动关闭⽅接收，从⽽帮助其正常关</p>
</li>
</ol>
<p>闭；</p>
<p><strong>原因1:防⽌旧连接的数据包</strong></p>
<p>假设 TIME-WAIT 没有等待时间或时间过短，被延迟的数据包抵达后会发⽣什么呢？</p>
<img src="timewait1.png" style="zoom:50%;" />



<ul>
<li><p>如上图⻩⾊框框服务端在关闭连接之前发送的 SEQ &#x3D; 301 报⽂，被⽹络延迟了。</p>
</li>
<li><p>这时有相同端⼝的 TCP <strong>连接被复⽤</strong>后，被延迟的 SEQ &#x3D; 301 抵达了客户端，那么客户端是有可能正常接收</p>
<p>这个过期的报⽂，这就会产⽣数据错乱等严᯿的问题。</p>
</li>
</ul>
<p>所以，TCP 就设计出了这么⼀个机制，经过 <strong>2MSL</strong> 这个时间，<strong>⾜以让两个⽅向上的数据包都被丢弃</strong>，使得原来</p>
<p>连接的数据包在⽹络中都⾃然消失，再出现的数据包⼀定都是新建⽴连接所产⽣的。</p>
<p><strong>原因2:保证连接正确关闭</strong></p>
<p>在 RFC 793 指出 TIME-WAIT 另⼀个重要的作⽤是：</p>
<blockquote>
<p> <em>TIME-WAIT - represents waiting for enough time to pass to be sure the remote TCP received theacknowledgment of its connection termination request.</em></p>
</blockquote>
<p>也就是说，TIME-WAIT 作⽤是<strong>等待⾜够的时间以确保最后的ACK能让被动关闭⽅接收</strong>，从⽽帮助其正常关闭。</p>
<p>假设 TIME-WAIT 没有等待时间或时间过短，断开连接会造成什么问题呢？</p>
<img src="timewait2.png" style="zoom:50%;" />



<ul>
<li><p>如上图红⾊框框客户端四次挥⼿的最后⼀个 ACK 报⽂如果在⽹络中被丢失了，此时如果客户端 TIME-WAIT 过短或没有，则就直接进⼊了 CLOSED 状态了，那么服务端则会⼀直处在 LASE_ACK 状态。</p>
</li>
<li><p>当客户端发起建⽴连接的 SYN 请求报⽂后，服务端会发送 RST 报⽂给客户端，连接建⽴的过程就会被</p>
</li>
</ul>
<p>终⽌。</p>
<p>如果 TIME-WAIT 等待⾜够⻓的情况就会遇到两种情况：</p>
<ul>
<li><p>服务端正常收到四次挥⼿的最后⼀个 ACK 报⽂，则服务端正常关闭连接。</p>
</li>
<li><p>服务端没有收到四次挥⼿的最后⼀个 ACK 报⽂时，则会重发 FIN 关闭连接报⽂并等待新的 ACK 报</p>
</li>
</ul>
<p>⽂。</p>
<p>所以客户端在 TIME-WAIT 状态等待 2MSL 时间后，就可以保证双⽅的连接都可以正常的关闭。</p>
<blockquote>
<p> 一个极端的情况：如果客户端第四次挥⼿ack丢失，服务端超时重发的fin报⽂也丢失，客户端timewait时间超过了2msl，这个时候会发⽣什么？认为连接已经关闭吗？</p>
<p>当客户端 timewait 时间超过了 2MSL，则客户端就直接进⼊关闭状态。</p>
<p>服务端超时重发 fin 报⽂的次数如果超过 <code>tcp_orphan_retries</code> ⼤⼩后，服务端也会关闭 TCP 连接。</p>
</blockquote>
<h3 id="为什么-TIME-WAIT-等待的时间是-2MSL"><a href="#为什么-TIME-WAIT-等待的时间是-2MSL" class="headerlink" title="为什么 TIME_WAIT 等待的时间是 2MSL"></a>为什么 TIME_WAIT 等待的时间是 2MSL</h3><p><strong>MSL</strong> 是 <code>Maximum Segment Lifetime</code>，报⽂最⼤⽣存时间，<strong>它是任何 TCP 报⽂在⽹络上存在的最⻓时间</strong>，超过这个时</p>
<p>间报⽂将被丢弃。因为 TCP 报⽂基于是 IP 协议的，⽽ IP 头中有⼀个 TTL 字段，是 IP 数据报可以经过的最⼤路</p>
<p>由数，每经过⼀个处理他的路由器此值就减 1，当此值为 0 则数据报将被丢弃，同时发送 ICMP 报⽂通知源主机。</p>
<blockquote>
<p>MSL 与 TTL 的区别： MSL 的单位是时间;</p>
<p>TTL 是经过路由跳数。所以 <strong>MSL</strong> 应该要⼤于等于 <strong>TTL</strong> 消耗为 <strong>0</strong> 的时间，以确保报⽂已被⾃然消亡。</p>
</blockquote>
<p>TIME_WAIT 等待 2 倍的 MSL，⽐较合理的解释是： ⽹络中可能存在来⾃发送⽅的数据包，当这些发送⽅的数据包</p>
<p>被接收⽅处理后⼜会向对⽅发送响应，所以⼀来⼀回需要等待 2 倍的时间。</p>
<p>2 MSL 即是服务器端发出 FIN 报文和客户端发出的 ACK 报文所能保持有效的最大时长。</p>
<p><strong>第1个MSL用于被动关闭方等待最终ACK报文的超时，第2MSL用于被动关闭方重新发送FIN报文的传输时间。</strong></p>
<blockquote>
<p>如果重传的 Fin 也丢失了咋办，超过了两个2msl的时间。主动方（举例里客户端）也close了。而被动方（举例里服务端）还在last-ack阶段，会不会出问题。感觉这种情况有点极端就是了。</p>
</blockquote>
<p>⽐如，如果被动关闭⽅没有收到断开连接的最后的 ACK 报⽂，就会触发超时重发 Fin 报⽂，另⼀⽅接收到 FIN 后，会重发 ACK 给被动关闭⽅， ⼀来⼀去正好 2 个 MSL。</p>
<p><strong>2MSL 的时间是从客户端接收到FIN后发送ACK后开始计时的</strong>。如果在 TIME-WAIT 时间内，因为客户端的 ACK<br>没有传输到服务端，客户端⼜接收到了服务端重发的 FIN 报⽂，那么 <strong>2MSL</strong> 时间将重新计时。 </p>
<p>在 Linux 系统⾥ 2MSL 默认是 60 秒，那么⼀个 MSL 也就是 30 秒。<strong>Linux</strong> 系统停留在 <strong>TIME_WAIT</strong> 的时间为固定的 <strong>60</strong> 秒。</p>
<p>其定义在 Linux 内核代码⾥的名称为 TCP_TIMEWAIT_LEN：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> TCP_TIMEWAIT_LEN (60*HZ) <span class="comment">/* how long to wait to destroy TIME-WAIT</span></span></span><br><span class="line"><span class="comment"><span class="meta"></span></span></span><br><span class="line"><span class="comment"><span class="meta">state, about 60 seconds *</span></span></span><br></pre></td></tr></table></figure>

<p>如果要修改 TIME_WAIT 的时间⻓度，只能修改 Linux 内核代码⾥<code>TCP_TIMEWAIT_LEN</code>的值，并<strong>重新编译 Linux</strong></p>
<p><strong>内核</strong>。</p>
<h3 id="TIME-WAIT-过多的危害"><a href="#TIME-WAIT-过多的危害" class="headerlink" title="TIME_WAIT 过多的危害"></a>TIME_WAIT 过多的危害</h3><p>考虑高并发短连接的业务场景，在<strong>高并发短连接</strong>的 TCP 服务器上，当服务器处理完请求后主动请求关闭连接，这样服务器上会有大量的连接处于 TIME_WAIT 状态。服务器维护每一个连接需要一个 socket，也就是每个连接会占用一个文件描述符，而文件描述符的使用是有上限的，如果持续高并发，会导致一些正常的连接失败。</p>
<p>所以，过多的 TIME-WAIT 状态主要的危害有两种：</p>
<ol>
<li><p>内存资源占⽤；</p>
</li>
<li><p>对端⼝资源的占⽤，⼀个 TCP 连接⾄少消耗⼀个本地端⼝；</p>
</li>
</ol>
<p>第⼆个危害是会造成严重的后果的，要知道，端⼝资源也是有限的，⼀般可以开启的端⼝为 32768～61000 ，也</p>
<p>可以通过如下参数设置指定<code>net.ipv4.ip_local_port_range</code>。</p>
<p>如果发起连接⼀⽅的 <strong>TIME_WAIT</strong> 状态过多，占满了所有端⼝资源，则会导致⽆法创建新连接。</p>
<p>对于客户端和服务端而言， TIME-WAIT 状态的危害的侧重点并不一样：</p>
<ul>
<li><strong>客户端受端⼝资源限制</strong>：客户端TIME_WAIT过多，就会导致端⼝资源被占⽤，因为端⼝就65536个，被占满就会导致⽆法创建新连</li>
</ul>
<p>接。</p>
<ul>
<li><p><strong>服务端受系统资源限制</strong>：</p>
<p>由于⼀个 TCP 四元组表示 TCP 连接，理论上服务端可以建⽴很多连接，服务端只监听⼀个端⼝，但是会把</p>
<p>连接扔给处理线程，所以理论上监听的端⼝可以继续监听。但是线程池处理不了那么多⼀直不断的连接了。</p>
<p>所以当服务端出现⼤ᰁ TIMEWAIT 时，系统资源容易被耗尽。</p>
</li>
</ul>
<h3 id="如何解决TIME-WAIT过多"><a href="#如何解决TIME-WAIT过多" class="headerlink" title="如何解决TIME_WAIT过多"></a>如何解决TIME_WAIT过多</h3><p>这⾥给出优化 TIME-WAIT 的⼏个⽅式，都是有利有弊：</p>
<ol>
<li>打开 net.ipv4.tcp_tw_reuse 和 net.ipv4.tcp_timestamps 选项；</li>
<li>net.ipv4.tcp_max_tw_buckets</li>
<li>使⽤ SO_LINGER ，应⽤强制使⽤ RST 关闭。</li>
<li>使用 SO_REUSEADDR</li>
</ol>
<p><strong>⽅式⼀：<em>net.ipv4.tcp_tw_reuse</em> 和 <em>tcp_timestamps</em></strong></p>
<p>如下的 Linux 内核参数开启后，则可以复⽤处于 <strong>TIME_WAIT</strong> 的 <strong>socket</strong> 为新的连接所⽤。</p>
<p>有⼀点需要注意的是，<strong>tcp_tw_reuse</strong> 功能<strong>只能⽤于客户端</strong>（连接发起⽅），因为开启了该功能，在调⽤ <strong>connect()</strong> 函数时，内核会随机找⼀个 <strong>time_wait</strong> 状态超过 <strong>1</strong> 秒的连接给新的连接复⽤。</p>
<p><code>net.ipv4.tcp_tw_reuse = 1</code></p>
<p>使⽤这个选项，还有⼀个前提，需要打开对 TCP 时间戳的⽀持，即</p>
<p><code>net.ipv4.tcp_timestamps=1</code>(默认即为 1)</p>
<p>这个时间戳的字段是在 TCP 头部的option⾥，⽤于记录 TCP 发送⽅的当前时间戳和从对端接收到的最新时间</p>
<p>戳。</p>
<p><strong>由于引⼊了时间戳，我们在前⾯提到的 2MSL 问题就不复存在了，因为重复复的数据包会因为时间戳过期被⾃然丢弃</strong>。</p>
<p><strong>⽅式⼆：<em>net.ipv4.tcp_max_tw_buckets</em></strong></p>
<p>这个值默认为 18000，当系统中处于 TIME_WAIT 的连接⼀旦超过这个值时，系统就会将后⾯的 <strong>TIME_WAIT</strong> 连接</p>
<p>状态重置。</p>
<p>这个⽅法过于暴⼒，⽽且治标不治本，带来的问题远⽐解决的问题多，不推荐使⽤。</p>
<p><strong>⽅式三：程序中使⽤ <em>SO_LINGER</em></strong></p>
<p>我们可以通过设置 socket 选项，来设置调⽤ close 关闭连接⾏为。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">linger</span> <span class="title">so_linger</span>;</span></span><br><span class="line"></span><br><span class="line">so_linger.l_onoff = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">so_linger.l_linger = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">setsockopt(s, SOL_SOCKET, SO_LINGER, &amp;so_linger,<span class="keyword">sizeof</span>(so_linger));</span><br></pre></td></tr></table></figure>



<p>如果 l_onoff 为⾮ 0， 且 l_linger 值为 0，那么调⽤ close 后，会⽴该发送⼀个 RST 标志给对端，该 TCP 连接</p>
<p>将跳过四次挥⼿，也就跳过了 TIME_WAIT 状态，直接关闭。</p>
<p>但这为跨越 TIME_WAIT 状态提供了⼀个可能，不过是⼀个⾮常危险的⾏为，不值得提倡。</p>
<p>**方式四：socket设置SO_REUSEADDR **</p>
<p> SO_REUSEADDR 是⽤户态的选项，⽤于「连接的服务⽅」，⽤来告诉操作系统内核，如果端⼝已被占⽤，</p>
<p>但是 TCP 连接状态位于 TIME_WAIT ，可以重⽤端⼝。如果端⼝忙，⽽ TCP 处于其他状态，重⽤会有</p>
<p>“Address already in use” 的错误信息。</p>
<p>注意 <code>SO_REUSEADDR</code>和<code>net.ipv4.tcp_tw_reuse</code>的区别：</p>
<ol>
<li>tcp_tw_reuse 是内核选项，主要⽤在连接的发起⽅（客户端）。TIME_WAIT 状态的连接创建时间超过 1 秒</li>
</ol>
<p>后，新的连接才可以被复⽤，注意，这⾥是「连接的发起⽅」；</p>
<ol start="2">
<li>SO_REUSEADDR 是⽤户态的选项，⽤于「连接的服务⽅」，⽤来告诉操作系统内核，如果端⼝已被占⽤，</li>
</ol>
<p>但是 TCP 连接状态位于 TIME_WAIT ，可以᯿⽤端⼝。如果端⼝忙，⽽ TCP 处于其他状态，重⽤会有</p>
<p>“Address already in use” 的错误信息。</p>
<p>tcp_tw_reuse 是为了缩短 time_wait 的时间，避免出现⼤ᰁ的 time_wait 连接⽽占⽤系统资源，解决的是 accept</p>
<p>后的问题。</p>
<p>SO_REUSEADDR 是为了解决 time_wait 状态带来的端⼝占⽤问题，以及⽀持同⼀个 port 对应多个 ip，解决的是</p>
<p>bind 时的问题。</p>
<blockquote>
<p><a target="_blank" rel="noopener" href="https://leetcode-cn.com/leetbook/read/networks-interview-highlights/ek40dd/">计算机网络面试突击</a></p>
<p><a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E4%BC%A0%E8%BE%93%E6%8E%A7%E5%88%B6%E5%8D%8F%E8%AE%AE">传输控制协议</a></p>
<p><a target="_blank" rel="noopener" href="https://www.cnblogs.com/shengs/p/10140678.html">服务器最大TCP连接数及调优汇总</a></p>
</blockquote>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/11/15/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE%E5%B1%82%E6%AC%A1/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="SongyangJi">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="JsyBlog">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | JsyBlog">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2021/11/15/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE%E5%B1%82%E6%AC%A1/" class="post-title-link" itemprop="url">计算机网络协议层次</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2021-11-15 21:53:47" itemprop="dateCreated datePublished" datetime="2021-11-15T21:53:47+08:00">2021-11-15</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">更新于</span>
      <time title="修改时间：2022-09-30 00:12:54" itemprop="dateModified" datetime="2022-09-30T00:12:54+08:00">2022-09-30</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/" itemprop="url" rel="index"><span itemprop="name">计算机网络</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h1 id="OSI七层模型"><a href="#OSI七层模型" class="headerlink" title="OSI七层模型"></a>OSI七层模型</h1><img src="osi.png" alt="image.png" style="zoom:60%;" />



<p>OSI 模型全称为开放式通信系统互连参考模型，是国际标准化组织 ( ISO ) 提出的一个试图使各种计算机在世界范围内互连为网络的标准框架。</p>
<p> OSI 将计算机网络体系结构划分为七层，每一层实现各自的功能和协议，并完成与相邻层的接口通信。</p>
<p>OSI 的服务定义详细说明了各层所提供的服务。某一层的服务就是该层及其下各层的一种能力，它通过接口提供给更高一层。各层所提供的服务与这些服务是怎么实现的无关。</p>
<p><strong>协议讲的是同一层的之间的约定，服务是底层提供给高层的功能。</strong></p>
<ul>
<li><strong>应用层</strong></li>
</ul>
<p>应用层位于 OSI 参考模型的第七层，其作用是通过应用程序间的交互来完成特定的网络应用。该层协议定义了应用<strong>进程之间</strong>的交互规则，<strong>通过不同的应用层协议为不同的网络应用提供服务</strong>。</p>
<p>例如域名系统 <em>DNS</em>，支持万维网应用的 <em>HTTP</em> 协议，电子邮件系统采用的 <em>SMTP</em> 协议等。</p>
<p>在应用层交互的数据单元我们称之为报文。</p>
<ul>
<li><strong>表示层</strong></li>
</ul>
<p>表示层的作用是使通信的应用程序能够解释交换数据的含义，其位于 OSI 参考模型的第六层，向上为应用层提供服务，向下接收来自会话层的服务。</p>
<p>该层提供的服务主要包括<em>数据压缩，数据加密以及数据描述</em>。这使得应用程序不必担心在各台计算机中表示和存储的内部格式差异。</p>
<ul>
<li><strong>会话层</strong></li>
</ul>
<p>会话层就是负责建立、管理和终止表示层实体之间的通信会话。该层提供了<em>数据交换的定界和同步</em>功能，包括了<em>建立检查点和恢复方案</em>的方法。</p>
<ul>
<li><strong>传输层</strong></li>
</ul>
<p>传输层的主要任务是<strong>为两台主机进程之间的通信提供服务</strong>。</p>
<p>应用程序利用该服务传送应用层报文。该服务并不针对某一特定的应用，多种应用可以使用同一个传输层服务。由于一台主机可同时运行多个线程，因此传输层有复用和分用的功能。所谓复用就是指多个应用层进程可同时使用下面传输层的服务，分用和复用相反，是传输层把收到的信息分别交付上面应用层中的相应进程。</p>
<p>在传输层交互的数据单元我们称之为<em>段</em>。</p>
<ul>
<li><strong>网络层</strong></li>
</ul>
<p>两台计算机之间传送数据时其通信链路往往不止一条，所传输的信息甚至可能经过很多通信子网。</p>
<p><strong>网络层的主要任务就是选择合适的网间路由和交换节点，确保数据按时成功传送</strong>。在发送数据时，网络层把传输层产生的报文或用户数据报封装成分组和包向下传输到数据链路层。</p>
<p><strong>在网络层使用的协议是无连接的网际协议（Internet Protocol）和许多路由协议</strong>，因此我们通常把该层简单地称为 IP 层。</p>
<p>在网络层交互的数据单元我们称之为<em>包</em>。</p>
<ul>
<li><strong>数据链路层</strong></li>
</ul>
<p>数据链路层通常也叫做链路层，在物理层和网络层之间。两台主机之间的数据传输，总是在一段一段的链路上传送的，这就需要使用专门的链路层协议。</p>
<p>在两个相邻节点之间传送数据时，<strong>数据链路层将网络层交下来的 IP 数据报组装成帧</strong>，在两个相邻节点间的链路上传送帧。每一帧包括数据和必要的控制信息。通过控制信息我们可以知道一个帧的起止比特位置，此外，也能使接收端检测出所收到的帧有无差错，如果发现差错，数据链路层能够简单的丢弃掉这个帧，以避免继续占用网络资源。</p>
<p>在数据链路层交互的数据单元我们称之为<em>帧</em>。</p>
<ul>
<li><strong>物理层</strong></li>
</ul>
<p>作为 OSI 参考模型中最低的一层，<strong>物理层的作用是实现计算机节点之间比特流的透明传送</strong>，<strong>尽可能屏蔽掉具体传输介质和物理设备的差异</strong>。使其上面的数据链路层不必考虑网络的具体传输介质是什么。该层的主要任务是确定与传输媒体的接口的一些特性（机械特性、电气特性、功能特性，过程特性）。</p>
<p>在物理层交互的数据单元我们称之为<em>比特</em>。</p>
<h1 id="TCP-x2F-IP-参考模型"><a href="#TCP-x2F-IP-参考模型" class="headerlink" title="TCP&#x2F;IP 参考模型"></a>TCP&#x2F;IP 参考模型</h1><p> OSI 七层模型在提出时的出发点是基于标准化的考虑，而没有考虑到具体的市场需求，使得该模型结构复杂，部分功能冗余，因而完全实现 OSI 参考模型的系统不多。</p>
<p><strong>TCP&#x2F;IP 参考模型直接面向市场需求，实现起来也比较容易</strong>，因此在一经提出便得到了广泛的应用。基于 TCP&#x2F;IP 的参考模型将协议分成四个层次，如上图所示，它们自下而上分别是：网络访问层、网际互联层、传输层、和应用层。</p>
<ul>
<li><strong>应用层</strong></li>
</ul>
<p>TCP&#x2F;IP 模型将 OSI 参考模型中的<strong>会话层、表示层和应用层的功能合并到一个应用层实现</strong>，通过不同的应用层协议为不同的应用提供服务。例如：HTTP、FTP、Telnet、DNS、SMTP 等。</p>
<ul>
<li><strong>传输层</strong></li>
</ul>
<p>该层对应于 OSI 参考模型的传输层，为上层实体提供源端到对端主机的通信功能。</p>
<p>传输层定义了两个主要协议：传输控制协议（TCP）和用户数据报协议（UDP）。其中面向连接的 TCP 协议保证了数据的传输可靠性，面向无连接的 UDP 协议能够实现数据包简单、快速地传输。</p>
<ul>
<li><strong>网际互联层</strong></li>
</ul>
<p>网际互联层对应 OSI 参考模型的网络层，主要负责相同或不同网络中计算机之间的通信。</p>
<p>在网际互联层， <strong>IP 协议提供的是一个不可靠、无连接的数据报传递服务</strong>。</p>
<p><strong>IP协议本身是不可靠的，存在着丢包、延时、无序等问题。</strong></p>
<p>该协议实现两个基本功能：<strong>寻址和分段</strong>。根据数据报报头中的目的地址将数据传送到目的地址，在这个过程中 IP 负责选择传送路线。</p>
<p>除了 IP 协议外，该层另外两个主要协议是互联网组管理协议（IGMP）和互联网控制报文协议（ICMP）。</p>
<ul>
<li><strong>网络接入层</strong></li>
</ul>
<p><strong>网络接入层的功能对应于 OSI 参考模型中的物理层和数据链路层</strong>，它负责监视数据在主机和网络之间的交换。事实上，<strong>TCP&#x2F;IP 并未真正描述这一层的实现</strong>，而由参与互连的各网络使用自己的物理层和数据链路层协议，然后与 TCP&#x2F;IP 的网络接入层进行连接，因此具体的实现方法将随着网络类型的不同而有所差异。</p>
<p><strong>TCP&#x2F;IP 五层参考模型</strong></p>
<p>五层体系的协议结构是综合了 OSI 和 TCP&#x2F;IP 优点的一种协议，包括应用层、传输层、网络层、数据链路层和物理层。其中应用层对应 OSI 的上三层，下四层和 OSI 相同。五层协议的体系结构只是为介绍网络原理而设计的，实际应用还是 TCP&#x2F;IP 四层体系结构。</p>
<p>在《计算机网络》一书中也是根据这个层次划分进行编排的。</p>
<h1 id="OSI-模型和-TCP-x2F-IP-模型异同比较"><a href="#OSI-模型和-TCP-x2F-IP-模型异同比较" class="headerlink" title="OSI 模型和 TCP&#x2F;IP 模型异同比较"></a>OSI 模型和 TCP&#x2F;IP 模型异同比较</h1><h2 id="相同点"><a href="#相同点" class="headerlink" title="相同点"></a>相同点</h2><p>① OSI 参考模型与 TCP&#x2F;IP 参考模型都采用了层次结构。</p>
<p>② 都能够提供<strong>面向连接</strong>和<strong>无连接</strong>两种通信服务机制。</p>
<p>无连接套接字传输效率高，但是不可靠，有丢失数据包，捣乱数据的风险<br>如果注重效率和实时性，就选择无连接套接字（UDP服务），比如DNS和即时聊天软件等。<br>有连接套接字非常可靠，万无一失，但是传输效率低，耗费资源多<br>如果对数据完整性的要求高，就选择有连接套接字（TCP服务），比如HTTP和FTP等。</p>
<h2 id="不同点"><a href="#不同点" class="headerlink" title="不同点"></a>不同点</h2><p>① OSI 采用的七层模型； TCP&#x2F;IP 是四层结构。</p>
<p>② TCP&#x2F;IP 参考模型没有对网络接口层进行细分，只是一些概念性的描述； OSI 参考模型对服务和协议做了明确的区分。</p>
<p>③ OSI 先有模型，后有协议规范，适合于描述各种网络；TCP&#x2F;IP 是先有协议集然后建立模型，不适用于非 TCP&#x2F;IP 网络。</p>
<p>④ TCP&#x2F;IP 一开始就提出面向连接和无连接服务，而 OSI 一开始只强调面向连接服务，直到很晚才开始制定无连接的服务标准。</p>
<p>⑤ OSI 参考模型虽然被看好，但将网络划分为七层，实现起来较困难；相反，TCP&#x2F;IP 参考模型虽然有许多不尽人意的地方，但作为一种简化的分层结构还是比较成功的。</p>
<h2 id="为什么-TCP-x2F-IP-去除了表示层和会话层"><a href="#为什么-TCP-x2F-IP-去除了表示层和会话层" class="headerlink" title="为什么 TCP&#x2F;IP 去除了表示层和会话层"></a>为什么 TCP&#x2F;IP 去除了表示层和会话层</h2><p>OSI 参考模型在提出时，他们的理想是非常好的，但实际上，由于会话层、表示层、应用层都是在应用程序内部实现的，最终产出的是一个应用数据包，而应用程序之间是几乎无法实现代码的抽象共享的，这也就造成 OSI 设想中的应用程序维度的分层是无法实现的，例如，我们几乎不会认为数据的压缩、加密算法算是一种协议，而会话的概念则更为抽象，难以用协议来进行描述，所以在后来的 TCP&#x2F;IP 协议框架的设计中，便将表示层和会话层与应用层整合在一起，让整个过程更为清晰明了。</p>
<blockquote>
<p>三层都在应用程序内实现，程序间无法共享，合三为一；</p>
</blockquote>
<h2 id="数据如何在各层之间传输（数据的封装过程）"><a href="#数据如何在各层之间传输（数据的封装过程）" class="headerlink" title="数据如何在各层之间传输（数据的封装过程）"></a>数据如何在各层之间传输（数据的封装过程）</h2><p><img src="https://upload-images.jianshu.io/upload_images/16902285-d74d1c1b8d27e037.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/684/format/webp"></p>
<p>在发送主机端：</p>
<ol>
<li><p><strong>应用层</strong>向<strong>传输层</strong>传递应用层的报文：传输层收取到报文并附上附加信息，该首部将被接收端的传输层使用。应用层报文和传输层首部信息一道构成了传输层报文段。附加的信息可能包括：允许接收端传输层向上向适当的应用程序交付<strong>报文的信息</strong>以及<strong>差错检测位</strong>信息,该信息让接收端能够判断报文中的比特是否在途中已被改变。</p>
</li>
<li><p><strong>传输层</strong>向<strong>网络层</strong>传递该报文段：<strong>网络层增加了如源和目的端系统地址等网络层首部信息</strong>，生成了网络层数据报。</p>
</li>
<li><p><strong>网络层</strong>向<strong>链路层</strong>传递该分组，<strong>在数据链路层数据包添加发送端 MAC 地址和接收端 MAC 地址</strong>后被封装成数据帧。</p>
</li>
<li><p><strong>链路层</strong>向<strong>物理层</strong>传递帧：在物理层数据帧被封装成比特流，之后通过传输介质传送到对端。</p>
</li>
</ol>
<p><strong>应用数据报→传输层报文段→网络层成组→链路层成帧→物理层比特流</strong></p>
<h1 id="OSI-和-TCP-x2F-IP-协议之间的对应关系"><a href="#OSI-和-TCP-x2F-IP-协议之间的对应关系" class="headerlink" title="OSI 和 TCP&#x2F;IP 协议之间的对应关系"></a>OSI 和 TCP&#x2F;IP 协议之间的对应关系</h1><table>
<thead>
<tr>
<th>OSI 七层网络模型</th>
<th>TCP&#x2F;IP 四层概念模型</th>
<th>对应的网络协议</th>
</tr>
</thead>
<tbody><tr>
<td>应用层（Application）</td>
<td>应用层</td>
<td>HTTP, TFTP, FTP, NFS, WAIS, SMTP, Telnet, DNS, SNMP</td>
</tr>
<tr>
<td>表示层（Presentation）</td>
<td>~</td>
<td>TIFF, GIF, JPEG, PICT</td>
</tr>
<tr>
<td>会话层（Session）</td>
<td>~</td>
<td>RPC, SQL, NFS, NetBIOS, names, AppleTalk</td>
</tr>
<tr>
<td>传输层（Transport）</td>
<td>传输层</td>
<td>TCP, UDP</td>
</tr>
<tr>
<td>网络层（Network）</td>
<td>网络层</td>
<td>IP, ICMP, ARP, RARP, RIP, IPX</td>
</tr>
<tr>
<td>数据链路层（Data Link）</td>
<td>数据链路层</td>
<td>FDDI, Frame Relay, HDLC, SLIP, PPP</td>
</tr>
<tr>
<td>物理层（Physical）</td>
<td>~</td>
<td>EIA&#x2F;TIA-232, EIA&#x2F;TIA-499, V.35, 802.3</td>
</tr>
</tbody></table>
<p>在TCP&#x2F;IP模型中，ARP协议属于网络层；在OSI模型中，ARP协议属于链路层。</p>
<blockquote>
<p>其实在书上说在哪里都可以啊<br>ARP 其实是介于数据链路层和网络层之间的协议的。</p>
</blockquote>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/11/13/JUC%E4%B9%8BLock%E3%80%81Condition%E6%8E%A5%E5%8F%A3%E4%B8%8E%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="SongyangJi">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="JsyBlog">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | JsyBlog">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2021/11/13/JUC%E4%B9%8BLock%E3%80%81Condition%E6%8E%A5%E5%8F%A3%E4%B8%8E%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/" class="post-title-link" itemprop="url">JUC之Lock、Condition接口与实现原理</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2021-11-13 18:12:00" itemprop="dateCreated datePublished" datetime="2021-11-13T18:12:00+08:00">2021-11-13</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">更新于</span>
      <time title="修改时间：2023-01-04 23:29:32" itemprop="dateModified" datetime="2023-01-04T23:29:32+08:00">2023-01-04</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/JUC/" itemprop="url" rel="index"><span itemprop="name">JUC</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h1 id="Lock接口"><a href="#Lock接口" class="headerlink" title="Lock接口"></a>Lock接口</h1><h2 id="Lock接口的API"><a href="#Lock接口的API" class="headerlink" title="Lock接口的API"></a>Lock接口的API</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> java.util.concurrent.locks;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Lock</span> &#123;</span><br><span class="line">    <span class="comment">// 获取锁，如果不能立即获取，阻塞，获取到锁之后从该方法返回</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">lock</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取锁，相比lock()方法，它可以相应中断</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">lockInterruptibly</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 非阻塞的尝试获取锁，如果能立即获取并返回true，否则立即返回false</span></span><br><span class="line">    <span class="type">boolean</span> <span class="title function_">tryLock</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 带超时的获取锁，发生下列情况之一时返回</span></span><br><span class="line">    <span class="comment">// 1. 获取到锁</span></span><br><span class="line">    <span class="comment">// 2. 超时</span></span><br><span class="line">    <span class="comment">// 3. 被中断</span></span><br><span class="line">    <span class="type">boolean</span> <span class="title function_">tryLock</span><span class="params">(<span class="type">long</span> time, TimeUnit unit)</span> <span class="keyword">throws</span> InterruptedException;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 释放锁</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">unlock</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取通知组件，该组件和该lock实例（监视器的概念）绑定</span></span><br><span class="line">    Condition <span class="title function_">newCondition</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>如何使用它</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Lock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentrantLock</span>();</span><br><span class="line">lock.lock();</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="comment">// do something</span></span><br><span class="line">&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">    lock.unlock();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>





<h2 id="Lock和synchronized的联系与区别"><a href="#Lock和synchronized的联系与区别" class="headerlink" title="Lock和synchronized的联系与区别"></a>Lock和synchronized的联系与区别</h2><p><strong>共同点</strong></p>
<p>都是Java中常用的同步工具，都实现了锁的语义，都有预期配套的监视器方法。</p>
<p><strong>Lock相比于synchronized的特性</strong></p>
<ol>
<li>相比于<code>synchronized</code>隐式地获取、释放锁，Lock放弃了这一小小的便利性，让程序员自己获取和释放锁，控制的粒度细很多，尤其是在多把锁的有序性获取和释放时，更是<code>synchronized</code>做不到的；</li>
<li>可中断的获取锁；</li>
<li>提供尝试性的获取锁</li>
<li>可以超时获取锁</li>
</ol>
<h1 id="Condition接口"><a href="#Condition接口" class="headerlink" title="Condition接口"></a>Condition接口</h1><p>任何一个Java类都隐式继承了<code>java.lang.Object</code>,Obejct中的<code>wait()</code>、<code>wait(long timeout)</code>、<code>notify()</code>、<code>notifyAll()</code>方法。这些方法和<code>syschrinized</code>关键字配合就可以实现经典的<strong>等待-通知模式</strong></p>
<p><code>Condition</code>接口也提供了类似Object的监视器方法，与<code>Lock</code>配合也能实现上述的效果，并且功能更完整。</p>
<p>对比</p>
<h2 id="Condition接口的API"><a href="#Condition接口的API" class="headerlink" title="Condition接口的API"></a>Condition接口的API</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> java.util.concurrent.locks;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Condition</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 使当前线程等待，直到它被唤醒或被中断。</span></span><br><span class="line">    <span class="comment">// 在此方法可以返回当前线程之前，必须重新获取与此条件关联的锁,当线程返回时，它保证持有这个锁。</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">await</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 使当前线程等待，直到它被唤醒（不受中断影响）</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">awaitUninterruptibly</span><span class="params">()</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 进入等待状态直到被通知、中断、超时（时间单位是纳秒）</span></span><br><span class="line">    <span class="type">long</span> <span class="title function_">awaitNanos</span><span class="params">(<span class="type">long</span> nanosTimeout)</span> <span class="keyword">throws</span> InterruptedException;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 与上面类似，不过可以指定时间单位</span></span><br><span class="line">    <span class="type">boolean</span> <span class="title function_">await</span><span class="params">(<span class="type">long</span> time, TimeUnit unit)</span> <span class="keyword">throws</span> InterruptedException;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 与上面类似，不过直接指定终点时间</span></span><br><span class="line">    <span class="type">boolean</span> <span class="title function_">awaitUntil</span><span class="params">(Date deadline)</span> <span class="keyword">throws</span> InterruptedException;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 唤醒一个等待在Condition上的线程，该线程从等待方法返回前必须获得与Condition相关联的锁</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">signal</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 唤醒所有等待在Condition上的线程，该线程从等待方法返回前必须获得与Condition相关联的锁</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">signalAll</span><span class="params">()</span>;</span><br><span class="line">  </span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>



<h2 id="Condition的实现分析"><a href="#Condition的实现分析" class="headerlink" title="Condition的实现分析"></a>Condition的实现分析</h2><img src="LockAndCondition.png" style="zoom:25%;" />





<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 只截取部分部分</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ConditionObject</span> <span class="keyword">implements</span> <span class="title class_">Condition</span>, java.io.Serializable &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">transient</span> Node firstWaiter;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">transient</span> Node lastWaiter;</span><br><span class="line"></span><br><span class="line">  </span><br><span class="line">        <span class="keyword">public</span> <span class="title function_">ConditionObject</span><span class="params">()</span> &#123; &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Internal methods</span></span><br><span class="line">				<span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>





<h1 id="公平锁和非公平锁"><a href="#公平锁和非公平锁" class="headerlink" title="公平锁和非公平锁"></a>公平锁和非公平锁</h1><p>公平锁：多个线程按照申请锁的顺序去获得锁，线程会直接进入队列去排队，永远都是队列的第一位才能得到锁。</p>
<ul>
<li>优点：所有的线程都能得到资源，不会饿死在队列中。</li>
<li>缺点：吞吐量会下降很多，队列里面除了第一个线程，其他的线程都会阻塞，cpu唤醒阻塞线程的开销会很大。</li>
</ul>
<p>非公平锁：多个线程去获取锁的时候，会直接去尝试获取，获取不到，再去进入等待队列，如果能获取到，就直接获取到锁。</p>
<ul>
<li>优点：可以减少CPU唤醒线程的开销，整体的吞吐效率会高点，CPU也不必取唤醒所有线程，会减少唤起线程的数量。</li>
<li>缺点：你们可能也发现了，这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁，导致饿死。</li>
</ul>
<p>学习AQS的时候，了解到AQS依赖于内部的FIFO同步队列来完成同步状态的管理，当前线程获取同步状态失败时，同步器会将当前线程以及等待状态等信息构造成一个Node对象并将其加入到同步队列，同时会阻塞当前线程，当同步状态释放时，会把首节点中的线程唤醒，使其再次尝试获取同步状态。</p>
<p>AQS的同步队列是FIFO的，就是先来排队的先走。那怎么实现非公平锁呢？</p>
<h2 id="ReentrantLock-的公平锁"><a href="#ReentrantLock-的公平锁" class="headerlink" title="ReentrantLock 的公平锁"></a>ReentrantLock 的公平锁</h2><p><strong>构造方法</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//默认</span></span><br><span class="line"><span class="keyword">public</span> <span class="title function_">ReentrantLock</span><span class="params">()</span> &#123;</span><br><span class="line">    sync = <span class="keyword">new</span> <span class="title class_">NonfairSync</span>();</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//传入true or false</span></span><br><span class="line"><span class="keyword">public</span> <span class="title function_">ReentrantLock</span><span class="params">(<span class="type">boolean</span> fair)</span> &#123;</span><br><span class="line">    sync = fair ? <span class="keyword">new</span> <span class="title class_">FairSync</span>() : <span class="keyword">new</span> <span class="title class_">NonfairSync</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><strong>公平锁的 lock 方法</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">FairSync</span> <span class="keyword">extends</span> <span class="title class_">Sync</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">lock</span><span class="params">()</span> &#123;</span><br><span class="line">        acquire(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// AbstractQueuedSynchronizer.acquire(int arg)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">acquire</span><span class="params">(<span class="type">int</span> arg)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (!tryAcquire(arg) &amp;&amp;</span><br><span class="line">            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))</span><br><span class="line">            selfInterrupt();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">tryAcquire</span><span class="params">(<span class="type">int</span> acquires)</span> &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">Thread</span> <span class="variable">current</span> <span class="operator">=</span> Thread.currentThread();</span><br><span class="line">        <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> getState();</span><br><span class="line">        <span class="keyword">if</span> (c == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// 1. 和非公平锁相比，这里多了一个判断：是否有线程在等待</span></span><br><span class="line">            <span class="keyword">if</span> (!hasQueuedPredecessors() &amp;&amp;</span><br><span class="line">                compareAndSetState(<span class="number">0</span>, acquires)) &#123;</span><br><span class="line">                setExclusiveOwnerThread(current);</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (current == getExclusiveOwnerThread()) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">nextc</span> <span class="operator">=</span> c + acquires;</span><br><span class="line">            <span class="keyword">if</span> (nextc &lt; <span class="number">0</span>)</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&quot;Maximum lock count exceeded&quot;</span>);</span><br><span class="line">            setState(nextc);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们可以看到，在注释1的位置，有个<code>!hasQueuedPredecessors()</code>条件，意思是说当前同步队列没有前驱节点（也就是没有线程在等待）时才会去<code>compareAndSetState(0, acquires)</code>使用CAS修改同步状态变量。所以就实现了公平锁，根据线程发出请求的顺序获取锁。</p>
<h2 id="非公平锁的lock方法"><a href="#非公平锁的lock方法" class="headerlink" title="非公平锁的lock方法"></a>非公平锁的lock方法</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">NonfairSync</span> <span class="keyword">extends</span> <span class="title class_">Sync</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">lock</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 2. 和公平锁相比，这里会直接先进行一次CAS，成功就返回了</span></span><br><span class="line">        <span class="keyword">if</span> (compareAndSetState(<span class="number">0</span>, <span class="number">1</span>))</span><br><span class="line">            setExclusiveOwnerThread(Thread.currentThread());</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            acquire(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// AbstractQueuedSynchronizer.acquire(int arg)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">acquire</span><span class="params">(<span class="type">int</span> arg)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (!tryAcquire(arg) &amp;&amp;</span><br><span class="line">            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))</span><br><span class="line">            selfInterrupt();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">tryAcquire</span><span class="params">(<span class="type">int</span> acquires)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> nonfairTryAcquire(acquires);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Performs non-fair tryLock.  tryAcquire is implemented in</span></span><br><span class="line"><span class="comment"> * subclasses, but both need nonfair try for trylock method.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">nonfairTryAcquire</span><span class="params">(<span class="type">int</span> acquires)</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">Thread</span> <span class="variable">current</span> <span class="operator">=</span> Thread.currentThread();</span><br><span class="line">    <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> getState();</span><br><span class="line">    <span class="keyword">if</span> (c == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">//3.这里也是直接CAS，没有判断前面是否还有节点。</span></span><br><span class="line">        <span class="keyword">if</span> (compareAndSetState(<span class="number">0</span>, acquires)) &#123;</span><br><span class="line">            setExclusiveOwnerThread(current);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (current == getExclusiveOwnerThread()) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">nextc</span> <span class="operator">=</span> c + acquires;</span><br><span class="line">        <span class="keyword">if</span> (nextc &lt; <span class="number">0</span>) <span class="comment">// overflow</span></span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&quot;Maximum lock count exceeded&quot;</span>);</span><br><span class="line">        setState(nextc);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>非公平锁的实现在刚进入lock方法时会直接使用一次CAS去尝试获取锁，不成功才会到acquire方法中，如注释2。而在nonfairTryAcquire方法中并没有判断是否有前驱节点在等待，直接CAS尝试获取锁，如注释3。由此实现了非公平锁。</p>
<p><strong>非公平锁和公平锁的两处不同：</strong></p>
<ol>
<li><p>非公平锁在调用 lock 后，首先就会调用 CAS 进行一次抢锁，如果这个时候恰巧锁没有被占用，那么直接就获取到锁返回了。</p>
</li>
<li><p>非公平锁在 CAS 失败后，和公平锁一样都会进入到 tryAcquire 方法，在 tryAcquire 方法中，如果发现锁这个时候被释放了（state &#x3D;&#x3D; 0），非公平锁会直接 CAS 抢锁，但是公平锁会判断等待队列是否有线程处于等待状态，如果有则不去抢锁，乖乖排到后面。</p>
</li>
</ol>
<p><strong>公平锁和非公平锁就这两点区别，如果这两次 CAS 都不成功，那么后面非公平锁和公平锁是一样的，都要进入到阻塞队列等待唤醒</strong>。</p>
<p>相对来说，</p>
<ol>
<li><p><strong>非公平锁会有更好的性能，因为它的吞吐量更大</strong>;</p>
</li>
<li><p>但<strong>非公平锁让获取锁的时间变得更加不确定，可能会导致在阻塞队列中的线程长期处于饥饿状态</strong>。</p>
</li>
</ol>
<p>上文说到的线程切换的开销，其实就是非公平锁效率高于公平锁的原因，因为非公平锁减少了线程挂起的几率，后来的线程有一定几率逃离被挂起的开销。</p>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/11/13/JUC%E4%B9%8BAQS%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B8%8E%E4%BD%BF%E7%94%A8/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="SongyangJi">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="JsyBlog">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | JsyBlog">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2021/11/13/JUC%E4%B9%8BAQS%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B8%8E%E4%BD%BF%E7%94%A8/" class="post-title-link" itemprop="url">JUC之AQS源码解析与使用</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2021-11-13 18:07:00" itemprop="dateCreated datePublished" datetime="2021-11-13T18:07:00+08:00">2021-11-13</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">更新于</span>
      <time title="修改时间：2021-11-25 20:17:26" itemprop="dateModified" datetime="2021-11-25T20:17:26+08:00">2021-11-25</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/JUC/" itemprop="url" rel="index"><span itemprop="name">JUC</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h1 id="从自己实现一把锁说起"><a href="#从自己实现一把锁说起" class="headerlink" title="从自己实现一把锁说起"></a>从自己实现一把锁说起</h1><p>《操作系统概念》一书中在第6章”同步“中，以CAS这样的原子命令实现了锁的语义，并且以它为基础实现了非忙等（busy waiting）的锁。</p>
<p>下面我们在Java中使用<code>AtomicBoolean</code>这个原子更新类实现</p>
<h2 id="代码实例"><a href="#代码实例" class="headerlink" title="代码实例"></a>代码实例</h2><p>众所周知，<code>i++</code>是经典的读改写操作，它不是原子的。</p>
<p>下面的代码在多线程环境下将一个数<code>x</code>自增N次，并观察在加锁和不加锁的情况下的结果。</p>
<p>结果，显示如果使用我们自己的实现的互斥锁，<code>x</code>最终等于N，也说明锁生效了。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LockByCAS</span> <span class="keyword">implements</span> <span class="title class_">Lock</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicBoolean</span> <span class="variable">isLocked</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicBoolean</span>(<span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">MyInteger</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="type">int</span> x;</span><br><span class="line">        <span class="keyword">public</span> <span class="title function_">MyInteger</span><span class="params">(<span class="type">int</span> x)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.x = x;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">void</span> <span class="title function_">incr</span><span class="params">()</span> &#123;</span><br><span class="line">            x++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getX</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> x;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        <span class="type">MyInteger</span> <span class="variable">integer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MyInteger</span>(<span class="number">0</span>);</span><br><span class="line">        <span class="keyword">final</span> <span class="type">int</span> <span class="variable">N</span> <span class="operator">=</span> <span class="number">1000000</span>;</span><br><span class="line">        <span class="type">Lock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SimpleMutex</span>();</span><br><span class="line">        <span class="type">CountDownLatch</span> <span class="variable">countDownLatch</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CountDownLatch</span>(N);</span><br><span class="line">        <span class="type">ExecutorService</span> <span class="variable">pool</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">10</span>);</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; N; i++) &#123;</span><br><span class="line">            pool.submit(() -&gt; &#123;</span><br><span class="line">                <span class="comment">// 你可以去掉这个，看看结果</span></span><br><span class="line">                lock.lock();</span><br><span class="line">                integer.incr();</span><br><span class="line">                lock.unlock();</span><br><span class="line">                countDownLatch.countDown();</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;</span><br><span class="line">        countDownLatch.await(); <span class="comment">// just wait all tasks to finish</span></span><br><span class="line">        pool.shutdown();</span><br><span class="line">        System.out.println(integer.getX());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">lock</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// busy waiting</span></span><br><span class="line">        <span class="keyword">while</span> (isLocked.compareAndExchange(<span class="literal">false</span>, <span class="literal">true</span>)) &#123;</span><br><span class="line">            Thread.<span class="keyword">yield</span>();</span><br><span class="line">        &#125;        </span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">unlock</span><span class="params">()</span> &#123;</span><br><span class="line">        isLocked.set(<span class="literal">false</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/* 省略其他接口的方法 */</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>需要注意的是<code>compareAndSet</code>和<code>compareAndExchange</code>的区别，<code>compareAndExchange</code>的返回值是旧值，</p>
<p>而<code>compareAndSet</code>的返回值是CAS是否成功。</p>
<p>所以，如果在Java8中，使用<code>compareAndSet</code>可以使用下面的代码代替上面的循环。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* notice that compareAndExchange is different from compareAndSet*/</span></span><br><span class="line">        <span class="keyword">for</span>(;;) &#123;</span><br><span class="line">            <span class="comment">// CAS succeed to jump the loop</span></span><br><span class="line">            <span class="keyword">if</span>(isLocked.compareAndSet(<span class="literal">false</span>, <span class="literal">true</span>)) &#123;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br></pre></td></tr></table></figure>



<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>来总结一下自己实现的锁的缺点。</p>
<ol>
<li>使用<code>while</code>+<code>yield</code>，实际上就是忙等，在线程竞争非常剧烈的时候，CPU资源被大大浪费。不好，我们后面会用<code>wait</code>+<code>notify</code>的范式解决这个问题，使得线程没有获得锁的时候可以放弃CPU资源。</li>
<li>使用了<code>AtomicBoolean</code>，以及编写了一些自旋锁实现的模板代码。如果要实现其它的同步工具，这样的模板代码还要再写一遍。况且，<code>AtomicBoolean</code>自己是怎么实现的呢？</li>
</ol>
<blockquote>
<p>实际上面的痛点也正是由AQS(抽象队列同步器)所要解决的问题了。</p>
</blockquote>
<p>我们不妨看一看使用AQS实现一个非忙等的互斥锁有多简单，再开始我们对AQS的介绍。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SimpleMutex</span> <span class="keyword">implements</span> <span class="title class_">Lock</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Our internal helper class</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Sync</span> <span class="keyword">extends</span> <span class="title class_">AbstractQueuedSynchronizer</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Acquires the lock if state is zero</span></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">tryAcquire</span><span class="params">(<span class="type">int</span> acquires)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> compareAndSetState(<span class="number">0</span>, <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Releases the lock by setting state to zero</span></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">tryRelease</span><span class="params">(<span class="type">int</span> releases)</span> &#123;</span><br><span class="line">            setState(<span class="number">0</span>); <span class="comment">// no need to use cas</span></span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Provides a Condition</span></span><br><span class="line">        <span class="keyword">public</span> Condition <span class="title function_">newCondition</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">AbstractQueuedSynchronizer</span>.ConditionObject();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// The sync object does all the hard work. We just forward to it.</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Sync</span> <span class="variable">sync</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Sync</span>();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">lock</span><span class="params">()</span> &#123;</span><br><span class="line">        sync.acquire(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">unlock</span><span class="params">()</span> &#123;</span><br><span class="line">        sync.release(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Condition <span class="title function_">newCondition</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> sync.newCondition();</span><br><span class="line">    &#125;</span><br><span class="line">  </span><br><span class="line">    <span class="comment">// 还有其他的加锁方法，这里省略。下面的 Mutex 会有完整的实现。</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>通过继承AQS并重写一些方法，就可以实现一份非忙等的锁（不过这个锁还有一些锁语义的检查没有实现，这里仅仅作一个展示罢了）。</p>
<h1 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h1><p><code>AbstractQueuedSynchronizer</code>抽象队列同步器（简称<strong>队列同步器</strong>），是用来构建锁、信号量等同步组件的一个基础模板类，它使用了一个int成员变量来表示同步状态，通过内置的FIFO队，来完成对那些因为竞争资源（这里的资源形式各式各样，比如锁就是一个典型的竞争资源）而等待的线程的排队工作。</p>
<h2 id="如何使用AQS"><a href="#如何使用AQS" class="headerlink" title="如何使用AQS"></a>如何使用AQS</h2><p>AQS是一个抽象类，因此必须有子类去实现它的抽象方法。</p>
<p>设计模式告诉我们，聚合由于继承。</p>
<p>所以常见的使用方式是：</p>
<p>在自定义的同步组件中，用一个静态内部类去继承AQS，实现它的抽象方法。自定义的同步组件使用这个AQS的子类去实现high-level的同步语义。</p>
<p>要将此类用作同步器的基础，请根据适用情况重新定义以下方法，</p>
<ol>
<li><code>tryAcquire</code></li>
<li><code>tryRelease</code></li>
<li><code>tryAcquireShared</code></li>
<li><code>tryReleaseShared</code></li>
<li><code>isHeldExclusively</code></li>
</ol>
<p>默认情况下，这些方法中的每一个都会抛出<code>UnsupportedOperationException </code>。 这些方法的实现必须是内部线程安全的，并且通常应该是立即返回的而不是阻塞的。</p>
<p>override这些方法是使用此类的唯一支持方式。 所有其他方法都被声明为final因为都是模板类的模板方法，无需也不能重写。</p>
<p>重写同步器指定的方法时,需要使用<code>getState</code> 、 <code>setState()</code>、<code>compareAndSetState</code>来检查或修改同步器的状态。</p>
<h2 id="AQS和同步组件的关系"><a href="#AQS和同步组件的关系" class="headerlink" title="AQS和同步组件的关系"></a>AQS和同步组件的关系</h2><p>同步组件面向同步组件的使用者，比如<code>ReentrantLock</code> ；</p>
<p>而AQS构建同步组件的实现者，它隐藏了很多实现具体的实现细节，实现者只需要实现AQS的若干方法即可。</p>
<p>锁和同步器很好地隔离了使用者和实现者所需关注的领域。同时省去实现者对大量模板方法的编写。</p>
<h1 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h1><h3 id="AQS用于访问和修改同步状态的方法"><a href="#AQS用于访问和修改同步状态的方法" class="headerlink" title="AQS用于访问和修改同步状态的方法"></a>AQS用于访问和修改同步状态的方法</h3><ul>
<li><code>getState()</code> : 获取当前同步状态；</li>
<li><code>setState(int newState)</code> 设置当前同步状态；</li>
<li><code>compareAndSetState(int expect, int update)</code>使用CAS设置当前的状态，该方法是原子的。</li>
</ul>
<h2 id="AQS的可重写的方法"><a href="#AQS的可重写的方法" class="headerlink" title="AQS的可重写的方法"></a>AQS的可重写的方法</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 独占式获取同步状态，该方法应该查询对象的状态是否符合预期，如果允许，再进行CAS设置同步状态；</span></span><br><span class="line"><span class="comment">// 如果此方法返回失败，acquire 方法可能会将线程排队（如果它尚未排队），直到收到来自某个其他线程的释放信号。 </span></span><br><span class="line"><span class="comment">// 获取到同步状态返回 true, 否则返回 false</span></span><br><span class="line"><span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">tryAcquire</span><span class="params">(<span class="type">int</span> arg)</span> &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UnsupportedOperationException</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 独占式释放同步状态，其他在等待获取同步状态的线程将有机会获取同步状态</span></span><br><span class="line"><span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">tryRelease</span><span class="params">(<span class="type">int</span> arg)</span> &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UnsupportedOperationException</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 共享式获取同步状态，该方法应该查询对象的状态是否允许在共享模式下获取它，如果允许则获取它。</span></span><br><span class="line"><span class="comment">// 如果此方法返回失败，acquire 方法可能会将线程排队（如果它尚未排队），直到收到来自某个其他线程的释放信号。</span></span><br><span class="line"><span class="keyword">protected</span> <span class="type">int</span> <span class="title function_">tryAcquireShared</span><span class="params">(<span class="type">int</span> arg)</span> &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UnsupportedOperationException</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 共享式释放同步状态</span></span><br><span class="line"><span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">tryReleaseShared</span><span class="params">(<span class="type">int</span> arg)</span> &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UnsupportedOperationException</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 查询同步器是否在独占模式下被线程使用，一般该方法表示同步器是否被该线程独占（参考线程获取到锁的状态）</span></span><br><span class="line"><span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">isHeldExclusively</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UnsupportedOperationException</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>





<h2 id="AQS提供的模板方法"><a href="#AQS提供的模板方法" class="headerlink" title="AQS提供的模板方法"></a>AQS提供的模板方法</h2><p>独占式和共享式获取的区别在于<strong>同一时刻能否有多个线程同时获取的同步状态</strong>。</p>
<p>（比如读写锁中，读-读共享，实际上就是因为可以同时共享的获取到同步状态）。</p>
<table>
<thead>
<tr>
<th>方法名称</th>
<th>描述</th>
</tr>
</thead>
<tbody><tr>
<td><strong>独占式的获取释放同步状态</strong></td>
<td>~</td>
</tr>
<tr>
<td>void acquire(int arg)</td>
<td>独占式获取同步状态，调用重写的<code>tryAcquire</code>后如果成功直接返回，否则进入同步队列等待。</td>
</tr>
<tr>
<td>void acquireInterruptibly(int arg)</td>
<td>与<code>acquire</code>类似，不过该方法响应中断。在当前线程在同步队列等待的时候，如果它被中断，则该方法会抛出&#96;InterruptedException·。</td>
</tr>
<tr>
<td>boolean tryAcquireNanos(int arg, long nanosTimeout)</td>
<td>在<code>acquireInterruptibly</code>的基础上加了超时限制，如果没有在限制时间内获取到同步状态，返回false。</td>
</tr>
<tr>
<td>boolean release(int arg)</td>
<td>独占式的释放同步状态，该方法会在释放同步状态后，将同步队列中第一个节点包含的线程唤醒。该方法会调用重写的<code>tryRelease</code></td>
</tr>
<tr>
<td><strong>共享式的获取释放同步状态</strong></td>
<td>~</td>
</tr>
<tr>
<td>void acquireShared(int arg)</td>
<td>共享式获取同步状态，如果不能获取到同步状态，进入同步队列等待，和独占式获取的主要区别在于同一时刻可以有多个线程获取到同步状态。</td>
</tr>
<tr>
<td>void acquireSharedInterruptibly(int arg)</td>
<td></td>
</tr>
<tr>
<td>boolean tryAcquireSharedNanos(int arg, long nanosTimeout)</td>
<td></td>
</tr>
<tr>
<td>boolean releaseShared(int arg)</td>
<td></td>
</tr>
<tr>
<td><strong>查询同步队列的情况</strong></td>
<td>~</td>
</tr>
<tr>
<td>boolean hasQueuedThreads()</td>
<td>查询是否有线程在等待获取</td>
</tr>
<tr>
<td>getFirstQueuedThread</td>
<td>返回队列中的第一个（等待时间最长的）线程，如果当前没有线程排队，则返回null 。</td>
</tr>
<tr>
<td>boolean isQueued(Thread thread)</td>
<td>如果给定线程当前正在排队，则返回 true。<br/>此实现遍历队列以确定给定线程的存在。</td>
</tr>
<tr>
<td>Collection<code>&lt;Thread&gt;</code>getQueuedThreads()</td>
<td>返回一个包含可能正在等待获取的线程的集合。</td>
</tr>
</tbody></table>
<h1 id="使用AQS实现简单的同步工具"><a href="#使用AQS实现简单的同步工具" class="headerlink" title="使用AQS实现简单的同步工具"></a>使用AQS实现简单的同步工具</h1><h2 id="不可重入的独占锁Mutex"><a href="#不可重入的独占锁Mutex" class="headerlink" title="不可重入的独占锁Mutex"></a>不可重入的独占锁Mutex</h2><p>这个 <code>Mutex</code> 就是对上面的 <em>自己实现锁</em> 的句号了，</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Mutex</span> <span class="keyword">implements</span> <span class="title class_">Lock</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Our internal helper class</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Sync</span> <span class="keyword">extends</span> <span class="title class_">AbstractQueuedSynchronizer</span> &#123;</span><br><span class="line">        <span class="comment">// Acquires the lock if state is zero</span></span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">tryAcquire</span><span class="params">(<span class="type">int</span> acquires)</span> &#123;</span><br><span class="line">            <span class="keyword">assert</span> acquires == <span class="number">1</span>; <span class="comment">// Otherwise unused</span></span><br><span class="line">            <span class="keyword">if</span> (compareAndSetState(<span class="number">0</span>, <span class="number">1</span>)) &#123;</span><br><span class="line">                <span class="comment">// 设置当前拥有独占访问权限的线程。</span></span><br><span class="line">                setExclusiveOwnerThread(Thread.currentThread());</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Releases the lock by setting state to zero</span></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">tryRelease</span><span class="params">(<span class="type">int</span> releases)</span> &#123;</span><br><span class="line">            <span class="keyword">assert</span> releases == <span class="number">1</span>; <span class="comment">// Otherwise unused</span></span><br><span class="line">            <span class="keyword">if</span> (!isHeldExclusively())  <span class="comment">// 只有抢到锁的才能释放锁</span></span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalMonitorStateException</span>();</span><br><span class="line">            setExclusiveOwnerThread(<span class="literal">null</span>);</span><br><span class="line">            setState(<span class="number">0</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isHeldExclusively</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="comment">// a data race, but safe due to out-of-thin-air guarantees</span></span><br><span class="line">            <span class="keyword">return</span> getExclusiveOwnerThread() == Thread.currentThread();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Reports whether in locked state</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isLocked</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> getState() != <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Provides a Condition</span></span><br><span class="line">        <span class="keyword">public</span> Condition <span class="title function_">newCondition</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">AbstractQueuedSynchronizer</span>.ConditionObject();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// The sync object does all the hard work. We just forward to it.</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Sync</span> <span class="variable">sync</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Sync</span>();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">lock</span><span class="params">()</span> &#123;</span><br><span class="line">        sync.acquire(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">tryLock</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> sync.tryAcquire(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">unlock</span><span class="params">()</span> &#123;</span><br><span class="line">        sync.release(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Condition <span class="title function_">newCondition</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> sync.newCondition();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">lockInterruptibly</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        sync.acquireInterruptibly(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">tryLock</span><span class="params">(<span class="type">long</span> timeout, TimeUnit unit)</span></span><br><span class="line">            <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        <span class="keyword">return</span> sync.tryAcquireNanos(<span class="number">1</span>, unit.toNanos(timeout));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isLocked</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> sync.isLocked();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isHeldByCurrentThread</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> sync.isHeldExclusively();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">hasQueuedThreads</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> sync.hasQueuedThreads();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>



<h2 id="简易的发令枪-BooleanLatch"><a href="#简易的发令枪-BooleanLatch" class="headerlink" title="简易的发令枪 BooleanLatch"></a>简易的发令枪 BooleanLatch</h2><p>如下图实现的是一个简单的<code>CountDownLatch</code>,</p>
<p>不过只需要调用<code>fire</code>就可以唤醒所有那些因为它而等待的线程。</p>
<p>值得注意的是，因为唤醒的时候是全部都要唤醒，并且完全可以有多个线程同时去调用<code>fire()</code>，</p>
<p>所以<strong>下面的对于同步状态的获取和释放都是共享式的</strong>。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BooleanLatch</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Sync</span> <span class="keyword">extends</span> <span class="title class_">AbstractQueuedSynchronizer</span> &#123;</span><br><span class="line">        <span class="type">boolean</span> <span class="title function_">isSignalled</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> getState() != <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">protected</span> <span class="type">int</span> <span class="title function_">tryAcquireShared</span><span class="params">(<span class="type">int</span> ignore)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> isSignalled() ? <span class="number">1</span> : -<span class="number">1</span>; <span class="comment">// &gt;= 0</span></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">tryReleaseShared</span><span class="params">(<span class="type">int</span> ignore)</span> &#123;</span><br><span class="line">            <span class="keyword">assert</span> ignore == <span class="number">1</span>; <span class="comment">// just use state 1</span></span><br><span class="line">            setState(<span class="number">1</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Sync</span> <span class="variable">sync</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Sync</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">fire</span><span class="params">()</span> &#123;</span><br><span class="line">        sync.releaseShared(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">await</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        sync.acquireSharedInterruptibly(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        <span class="type">BooleanLatch</span> <span class="variable">booleanLatch</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BooleanLatch</span>();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">8</span>; i++) &#123;</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    booleanLatch.await();</span><br><span class="line">                &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                &#125;</span><br><span class="line">                System.out.println(Thread.currentThread().getName() + <span class="string">&quot; start&quot;</span>);</span><br><span class="line">            &#125;).start();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">3</span>; i++) &#123;</span><br><span class="line">            TimeUnit.SECONDS.sleep(<span class="number">1</span>);</span><br><span class="line">            System.out.println(<span class="string">&quot;倒计时&quot;</span> + (<span class="number">3</span> - i));</span><br><span class="line">        &#125;</span><br><span class="line">        booleanLatch.fire();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="最多两个线程同时获得锁-TwinsLock"><a href="#最多两个线程同时获得锁-TwinsLock" class="headerlink" title="最多两个线程同时获得锁 TwinsLock"></a>最多两个线程同时获得锁 TwinsLock</h2><p>因为允许多个线程同时获取锁，所以这里的对于同步状态的获取都是共享式的。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TwinsLock</span> <span class="keyword">implements</span> <span class="title class_">Lock</span> &#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Sync</span> <span class="keyword">extends</span> <span class="title class_">AbstractQueuedSynchronizer</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="title function_">Sync</span><span class="params">()</span> &#123;</span><br><span class="line">            setState(<span class="number">2</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">protected</span> <span class="type">int</span> <span class="title function_">tryAcquireShared</span><span class="params">(<span class="type">int</span> arg)</span> &#123;</span><br><span class="line">            <span class="keyword">for</span> (; ; ) &#123; <span class="comment">// 直到成功或者当前不可获取锁（不可获取锁就要去等待）</span></span><br><span class="line">                <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> getState();</span><br><span class="line">                <span class="type">int</span> <span class="variable">newCount</span> <span class="operator">=</span> count - arg;</span><br><span class="line">                <span class="keyword">if</span> (newCount &lt; <span class="number">0</span> || compareAndSetState(count, newCount)) &#123;</span><br><span class="line">                    <span class="keyword">return</span> newCount;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">tryReleaseShared</span><span class="params">(<span class="type">int</span> arg)</span> &#123;</span><br><span class="line">            <span class="keyword">for</span> (; ; ) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> getState();</span><br><span class="line">                <span class="type">int</span> <span class="variable">newCount</span> <span class="operator">=</span> count + arg;</span><br><span class="line">                <span class="keyword">if</span> (compareAndSetState(count, newCount)) &#123;</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Sync</span> <span class="variable">sync</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Sync</span>();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">lock</span><span class="params">()</span> &#123;</span><br><span class="line">        sync.acquireShared(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">unlock</span><span class="params">()</span> &#123;</span><br><span class="line">        sync.releaseShared(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">lockInterruptibly</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        sync.acquireSharedInterruptibly(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">tryLock</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> sync.tryAcquireShared(<span class="number">1</span>) &gt;= <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">tryLock</span><span class="params">(<span class="type">long</span> time, TimeUnit unit)</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        <span class="keyword">return</span> sync.tryAcquireSharedNanos(<span class="number">1</span>, unit.toNanos(time));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Condition <span class="title function_">newCondition</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UnsupportedOperationException</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">sleep</span><span class="params">(<span class="type">int</span> time)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            TimeUnit.SECONDS.sleep(time);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            System.err.println(e.getMessage());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>测试代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">Lock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">TwinsLock</span>();</span><br><span class="line">    <span class="keyword">class</span> <span class="title class_">Worker</span> <span class="keyword">extends</span> <span class="title class_">Thread</span> &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            lock.lock();</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                System.out.println(Thread.currentThread().getName());</span><br><span class="line">                TwinsLock.sleep(<span class="number">2</span>);</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                lock.unlock();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">        <span class="type">Worker</span> <span class="variable">worker</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Worker</span>();</span><br><span class="line">        worker.setDaemon(<span class="literal">true</span>);</span><br><span class="line">        worker.start();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">        sleep(<span class="number">1</span>);</span><br><span class="line">        System.out.println();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>








<h1 id="AQS源码分析"><a href="#AQS源码分析" class="headerlink" title="AQS源码分析"></a>AQS源码分析</h1><h1 id="同步队列"><a href="#同步队列" class="headerlink" title="同步队列"></a>同步队列</h1><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">Node</span> &#123;</span><br><span class="line">        <span class="comment">/** Marker to indicate a node is waiting in shared mode */</span></span><br><span class="line">        <span class="comment">// 共享等待模式</span></span><br><span class="line">        <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Node</span> <span class="variable">SHARED</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Node</span>();</span><br><span class="line">        <span class="comment">/** Marker to indicate a node is waiting in exclusive mode */</span></span><br><span class="line">        <span class="comment">// 独占等待模式</span></span><br><span class="line">        <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Node</span> <span class="variable">EXCLUSIVE</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/** waitStatus value to indicate thread has cancelled */</span></span><br><span class="line">        <span class="comment">// 表明：由于在同步队列中等待的线程等待超时或被中断，需要从队列中取消等待，节点进入此状态就不再变化</span></span><br><span class="line">        <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">CANCELLED</span> <span class="operator">=</span>  <span class="number">1</span>;</span><br><span class="line">        <span class="comment">/** waitStatus value to indicate successor&#x27;s thread needs unparking */</span></span><br><span class="line">        <span class="comment">// 后继节点正在等待，而当前线程释放了同步状态或取消，会通知后继节点</span></span><br><span class="line">        <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">SIGNAL</span>    <span class="operator">=</span> -<span class="number">1</span>;</span><br><span class="line">        <span class="comment">/** waitStatus value to indicate thread is waiting on condition */</span></span><br><span class="line">        <span class="comment">// 节点当在等待队列里，节点相关的线程正在因某个Condition等待</span></span><br><span class="line">        <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">CONDITION</span> <span class="operator">=</span> -<span class="number">2</span>;</span><br><span class="line">        <span class="comment">// 表明下一次共享式同步状态获取将会无条件的传播下去</span></span><br><span class="line">        <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">PROPAGATE</span> <span class="operator">=</span> -<span class="number">3</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 尽可取为上述的枚举状态和初始化状态 0 ;</span></span><br><span class="line">        <span class="comment">// waitStatus &lt; 0, 表明需要给Node发信号做一些事情，反之无需</span></span><br><span class="line">        <span class="keyword">volatile</span> <span class="type">int</span> waitStatus;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 前驱</span></span><br><span class="line">        <span class="keyword">volatile</span> Node prev;</span><br><span class="line">        <span class="comment">// 后继</span></span><br><span class="line">        <span class="keyword">volatile</span> Node next;</span><br><span class="line">        <span class="comment">// 在构造时初始化并在使用后置 null</span></span><br><span class="line">        <span class="keyword">volatile</span> Thread thread;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 该字段的含义被复用：</span></span><br><span class="line">        <span class="comment">// 1. </span></span><br><span class="line">        Node nextWaiter;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 是否是共享式等待的节点类型</span></span><br><span class="line">        <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">isShared</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> nextWaiter == SHARED;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * Returns previous node, or throws NullPointerException if null.</span></span><br><span class="line"><span class="comment">         * Use when predecessor cannot be null.  The null check could</span></span><br><span class="line"><span class="comment">         * be elided, but is present to help the VM.</span></span><br><span class="line"><span class="comment">         *</span></span><br><span class="line"><span class="comment">         * <span class="doctag">@return</span> the predecessor of this node</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="keyword">final</span> Node <span class="title function_">predecessor</span><span class="params">()</span> <span class="keyword">throws</span> NullPointerException &#123;</span><br><span class="line">            <span class="type">Node</span> <span class="variable">p</span> <span class="operator">=</span> prev;</span><br><span class="line">            <span class="keyword">if</span> (p == <span class="literal">null</span>)</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>();</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                <span class="keyword">return</span> p;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        Node() &#123;    <span class="comment">// Used to establish initial head or SHARED marker</span></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        Node(Thread thread, Node mode) &#123;     <span class="comment">// Used by addWaiter</span></span><br><span class="line">            <span class="built_in">this</span>.nextWaiter = mode;</span><br><span class="line">            <span class="built_in">this</span>.thread = thread;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        Node(Thread thread, <span class="type">int</span> waitStatus) &#123; <span class="comment">// Used by Condition</span></span><br><span class="line">            <span class="built_in">this</span>.waitStatus = waitStatus;</span><br><span class="line">            <span class="built_in">this</span>.thread = thread;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 懒初始化，仅通过 setHead 修改（除了初始化的时候）</span></span><br><span class="line">    <span class="comment">// 首节点，总是获取同步状态成功的节点</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> Node head;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 尾节点，懒初始化，仅通过 enq 修改</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> Node tail;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 同步状态</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">int</span> state;</span><br></pre></td></tr></table></figure>



<h2 id="独占式同步状态的获取与释放"><a href="#独占式同步状态的获取与释放" class="headerlink" title="独占式同步状态的获取与释放"></a>独占式同步状态的获取与释放</h2><ul>
<li>同步器的独占式同步状态的获取：<code>acquire(int arg)</code></li>
</ul>
<p>先尝试一下获取同步状态，失败后线程安全地将节点加入<strong>同步队列</strong> (调用addWaiter)。</p>
<p>然后进入同步队列的那些节点而言，就进入了一个自旋过程，该节点以”死循环“的方式获取同步状态（调用acquireQueued）。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">acquire</span><span class="params">(<span class="type">int</span> arg)</span> &#123;</span><br><span class="line">       <span class="comment">// 先尝试一下获取同步状态，失败后</span></span><br><span class="line">       <span class="keyword">if</span> (!tryAcquire(arg) &amp;&amp;</span><br><span class="line">           acquireQueued(addWaiter(Node.EXCLUSIVE), arg))</span><br><span class="line">           selfInterrupt(); <span class="comment">// </span></span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure>





<ul>
<li>为当前线程和给定模式创建和排队节点。</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> Node <span class="title function_">addWaiter</span><span class="params">(Node mode)</span> &#123;</span><br><span class="line">        <span class="type">Node</span> <span class="variable">node</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Node</span>(Thread.currentThread(), mode);</span><br><span class="line">        <span class="comment">// Try the fast path of enq; backup to full enq on failure</span></span><br><span class="line">        <span class="comment">// 先快速尝试一下</span></span><br><span class="line">        <span class="type">Node</span> <span class="variable">pred</span> <span class="operator">=</span> tail;</span><br><span class="line">        <span class="keyword">if</span> (pred != <span class="literal">null</span>) &#123;</span><br><span class="line">            node.prev = pred;</span><br><span class="line">            <span class="keyword">if</span> (compareAndSetTail(pred, node)) &#123; <span class="comment">// cas 设置尾巴</span></span><br><span class="line">                pred.next = node;</span><br><span class="line">                <span class="keyword">return</span> node;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 如果上面的 cas 失败再调用 enq(node);</span></span><br><span class="line">        enq(node);</span><br><span class="line">        <span class="keyword">return</span> node;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>



<ul>
<li>将节点插入队列，必要时进行初始化。</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 返回入队节点 node 的前驱</span></span><br><span class="line"><span class="keyword">private</span> Node <span class="title function_">enq</span><span class="params">(<span class="keyword">final</span> Node node)</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">            <span class="type">Node</span> <span class="variable">t</span> <span class="operator">=</span> tail;</span><br><span class="line">            <span class="keyword">if</span> (t == <span class="literal">null</span>) &#123; <span class="comment">// Must initialize</span></span><br><span class="line">                <span class="keyword">if</span> (compareAndSetHead(<span class="keyword">new</span> <span class="title class_">Node</span>()))</span><br><span class="line">                    tail = head;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                node.prev = t;</span><br><span class="line">                <span class="keyword">if</span> (compareAndSetTail(t, node)) &#123;</span><br><span class="line">                    t.next = node;</span><br><span class="line">                    <span class="keyword">return</span> t;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>



<p>对于进入同步队列的节点，会进入一个自旋的过程：</p>
<p>在<strong>前驱为head</strong>时，尝试获取同步状态（调用tryAcquire）,如果获取到同步状态，则退出自旋；</p>
<p>否则进入阻塞（调用parkAndCheckInterrupt）。</p>
<p>再它被唤醒的时候再次进行上面的尝试，一直这样循环下去。</p>
<img src="aqs-acquire.png" alt="aqs-acquire" style="zoom:50%;" />



<ul>
<li>以独占不响应中断的模式获取同步状态</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">  <span class="comment">// 如果在等待时中断，则返回true</span></span><br><span class="line">  <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">acquireQueued</span><span class="params">(<span class="keyword">final</span> Node node, <span class="type">int</span> arg)</span> &#123;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">failed</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="type">boolean</span> <span class="variable">interrupted</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">            <span class="keyword">final</span> <span class="type">Node</span> <span class="variable">p</span> <span class="operator">=</span> node.predecessor();</span><br><span class="line">            <span class="keyword">if</span> (p == head &amp;&amp; tryAcquire(arg)) &#123;</span><br><span class="line">                setHead(node);</span><br><span class="line">                p.next = <span class="literal">null</span>; <span class="comment">// help GC</span></span><br><span class="line">                failed = <span class="literal">false</span>;</span><br><span class="line">                <span class="keyword">return</span> interrupted;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (shouldParkAfterFailedAcquire(p, node) &amp;&amp;</span><br><span class="line">                parkAndCheckInterrupt())</span><br><span class="line">                interrupted = <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (failed)</span><br><span class="line">            cancelAcquire(node);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>头结点的线程释放了同步状态之后，将唤醒其后继节点，后继节点的线程被唤醒之后在去尝试获取同步状态。</p>
<p>不过需要注意，一定要有<code>p == head </code>的测试，<strong>因为只有首节点才拥有同步状态，但是非首节点有可能因为中断而被唤醒</strong>（也就是说要防止过早通知的情况）。</p>
<ul>
<li>可中断的获取同步状态</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">acquireInterruptibly</span><span class="params">(<span class="type">int</span> arg)</span></span><br><span class="line">        <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">    <span class="keyword">if</span> (Thread.interrupted())</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">InterruptedException</span>();</span><br><span class="line">    <span class="keyword">if</span> (!tryAcquire(arg))</span><br><span class="line">        doAcquireInterruptibly(arg);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 和上面的并没有什么差别，只是会直接抛出异常</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">doAcquireInterruptibly</span><span class="params">(<span class="type">int</span> arg)</span></span><br><span class="line">    <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">Node</span> <span class="variable">node</span> <span class="operator">=</span> addWaiter(Node.EXCLUSIVE);</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">failed</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">            <span class="keyword">final</span> <span class="type">Node</span> <span class="variable">p</span> <span class="operator">=</span> node.predecessor();</span><br><span class="line">            <span class="keyword">if</span> (p == head &amp;&amp; tryAcquire(arg)) &#123;</span><br><span class="line">                setHead(node);</span><br><span class="line">                p.next = <span class="literal">null</span>; <span class="comment">// help GC</span></span><br><span class="line">                failed = <span class="literal">false</span>;</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (shouldParkAfterFailedAcquire(p, node) &amp;&amp;</span><br><span class="line">                parkAndCheckInterrupt())</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">InterruptedException</span>(); <span class="comment">// 如果在等待中被中断，直接抛出 InterruptedException</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (failed)</span><br><span class="line">            cancelAcquire(node);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>





<h2 id="共享式同步状态的获取与释放"><a href="#共享式同步状态的获取与释放" class="headerlink" title="共享式同步状态的获取与释放"></a>共享式同步状态的获取与释放</h2><p>先挖个坑。。。</p>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




  <nav class="pagination">
    <a class="extend prev" rel="prev" title="上一页" aria-label="上一页" href="/page/18/"><i class="fa fa-angle-left"></i></a><a class="page-number" href="/">1</a><span class="space">&hellip;</span><a class="page-number" href="/page/18/">18</a><span class="page-number current">19</span><a class="page-number" href="/page/20/">20</a><span class="space">&hellip;</span><a class="page-number" href="/page/26/">26</a><a class="extend next" rel="next" title="下一页" aria-label="下一页" href="/page/20/"><i class="fa fa-angle-right"></i></a>
  </nav>

</div>
  </main>

  <footer class="footer">
    <div class="footer-inner">


<div class="copyright">
  &copy; 
  <span itemprop="copyrightYear">2023</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">SongyangJi</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" rel="noopener" target="_blank">Hexo</a> & <a href="https://theme-next.js.org/muse/" rel="noopener" target="_blank">NexT.Muse</a> 强力驱动
  </div>

    </div>
  </footer>

  
  <div class="toggle sidebar-toggle" role="button">
    <span class="toggle-line"></span>
    <span class="toggle-line"></span>
    <span class="toggle-line"></span>
  </div>
  <div class="sidebar-dimmer"></div>
  <div class="back-to-top" role="button" aria-label="返回顶部">
    <i class="fa fa-arrow-up fa-lg"></i>
    <span>0%</span>
  </div>

<noscript>
  <div class="noscript-warning">Theme NexT works best with JavaScript enabled</div>
</noscript>


  
  <script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js" integrity="sha256-XL2inqUJaslATFnHdJOi9GfQ60on8Wx1C2H8DYiN1xY=" crossorigin="anonymous"></script>
<script src="/js/comments.js"></script><script src="/js/utils.js"></script><script src="/js/motion.js"></script><script src="/js/schemes/muse.js"></script><script src="/js/next-boot.js"></script>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/hexo-generator-searchdb/1.4.1/search.js" integrity="sha256-1kfA5uHPf65M5cphT2dvymhkuyHPQp5A53EGZOnOLmc=" crossorigin="anonymous"></script>
<script src="/js/third-party/search/local-search.js"></script>





  





</body>
</html>
